Implement Corrected Commit Date
Re: [PATCH v3 09/11] commit-graph: use generation v2 only if entire chain does
In reply to Abhishek KumarHi Abhishek,
Quoted 5 lines · Abhishek Kumar
Quoted 4 lines · Jakub Narębski
Hi Abhishek,
...
Quoted 9 lines
Quoted 2 lines
However the commit message do not say anything about the *writing* side.
Revised the commit message to include the following at the end:
When writing the new layer in split commit-graph, we write a GDAT chunk only if the topmost layer has a GDAT chunk. This guarantees that if a layer has GDAT chunk, all lower layers must have a GDAT chunk as well.
All right.
Quoted 4 lines
Rewriting layers follows similar approach: if the topmost layer below set of layers being rewritten (in the split commit-graph chain) exists, and it does not contain GDAT chunk, then the result of rewrite does not have GDAT chunks either.
All right.
I see that you went with proposed more complex (but better) solution...
Quoted 6 lines
Quoted 6 lines
...
To be more detailed, without '--split=replace' we would want the following layer merging behavior:
Quoted 7 lines
Quoted 7 lines
[layer with GDAT][with GDAT][without GDAT][without GDAT][without GDAT]
1 2 3 4 5In the split commit-graph chain above, merging two topmost layers (layers 4 and 5) should create a layer without GDAT; merging three topmost layers (and any other layers, e.g. two middle ones, i.e. 3 and
4) should create a new layer with GDAT.
A simpler solution would be to create a new merged layer without GDAT if any of the layers being merged do not have GDAT.
In this solution merging 3+4+5, 3+4, and even 2+3 would result with layer without GDAT, and only merging 1+2 would result in layer with GDAT.
Quoted 24 lines
Quoted 9 lines
[layer with GDAT][with GDAT][without GDAT][-------without GDAT-------]
1 2 3 merged[layer with GDAT][with GDAT][-------------with GDAT------------------]
1 2 mergedI hope those ASCII-art pictures help understanding it
Thanks! There were helpful.
While we work as expected in the first scenario i.e merging 4 and 5, we would *still* write a layer without GDAT in the second scenario.
I have tweaked
split_graph_merge_strategy()to fix this:----------------------------------------------
commit-graph.c +0
-0
commit-graph.c static void split_graph_merge_strategy(struct write_commit_graph_context *ctx) · line 1981
Quoted 9 lines
commit-graph.c +3
-0@@ -1973,6 +1973,9 @@ static void split_graph_merge_strategy(struct write_commit_graph_context *ctx) } } +if (!ctx->write_generation_data && g->chunk_generation_data) + ctx->write_generation_data =1 ; +if (flags !=COMMIT_GRAPH_SPLIT_REPLACE ) ctx->new_base_graph = g;else if (ctx->num_commit_graphs_after !=1 )
...which turned out to be not that complicated. Nice work!
Though this needs tests that if fulfills the stated condition (because I am not sure if it is entirely correct: we are not checking the layer below current one, isn't it?... ah, you explain it below).
One possible solution would be to grep test-tool read-graph output for "^chunks: ", then pass it through uniq (without sort!), check that the number of lines is less or equal 2, and if there are two lines then check that we get the following contents:
chunks: oid_fanout oid_lookup commit_metadata generation_data
chunks: oid_fanout oid_lookup commit_metadata
(assuming that information about layers is added in top-down order).
This test must be run with GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=0, which I think is the default.
Quoted 6 lines
----------------------------------------------------
That is, if we were not writing generation data (because of mixed generation concerns) but the new topmost layer has a generation data chunk, we have merged all layers without GDAT chunk and can now write a GDAT chunk safely.
All right.
[...]
Quoted 15 lines
Quoted 7 lines
Quoted 7 lines
commit-graph.h +0
-0@@ -63,6 +63,7 @@ struct commit_graph {struct object_directory *odb;Quoted 7 lines
commit-graph.h struct commit_graph { · line 68
Quoted 4 lines
commit-graph.h +1
-0uint32_t num_commits_in_base; +uint32_t read_generation_data;struct commit_graph *base_graph;First, why
read_generation_datais ofuint32_ttype, when it stores (as far as I understand it), a "boolean" value of either 0 or 1?
Quoted 3 lines
Yes, using unsigned int instead of
uint32_t(although in most of cases it would be same). Ifcommit_graphhad other flags as well, we could have used a bit field.
OK.
Quoted 6 lines
Quoted 3 lines
Second, couldn't we simply set
chunk_generation_datato NULL? Or would that interfere with the case of rewriting, where we want to use existing GDAT data when writing new commit-graph with GDAT chunk?It interferes with rewriting the split commit-graph, as you might have guessed from the above code snippet.
All right.
[...]
Quoted 13 lines
Quoted 12 lines
commit-graph.h void load_commit_graph_info(struct repository *r, struct commit *item) · line 914
Quoted 8 lines
commit-graph.h +1
-0@@ -885,6 +908,7 @@ void load_commit_graph_info(struct repository *r, struct commit *item)uint32_t pos;if (!prepare_commit_graph (r))return ; +if (find_commit_in_graph (item, r->objects->commit_graph, &pos))fill_commit_graph_info (item, r->objects->commit_graph, pos); }This is unrelated whitespace fix, a "while at it" in neighbourhood of changes. All right then.
Quoted 1 line
Reverted this change, as it's unimportant.
Actually I am not against fixing the whitespace in the neighbourhood of changes, so you can keep it or revert it (discard).
Quoted 9 lines
Quoted 7 lines
commit-graph.h int write_commit_graph(struct object_directory *odb,
Quoted 1 line
commit-graph.h +0
-0@@ -2192,6 +2216,9 @@ int write_commit_graph(struct object_directory *odb,...
It would be nice to have an example with merging layers (whether we would handle it in strict or relaxed way).
Sure, will add.
Thanks.
Best,
--
Jakub Narębski
PATCH v3 10/11 commit-reach: use corrected commit dates in paint_down_to_common()
In reply to Abhishek Kumar via GitGitGadgetFrom: Abhishek Kumar <[email protected]>
With corrected commit dates implemented, we no longer have to rely on commit date as a heuristic in paint_down_to_common().
t6024-recursive-merge setups a unique repository where all commits have the same committer date without well-defined merge-base. As this has already caused problems (as noted in 859fdc0 (commit-graph: define GIT_TEST_COMMIT_GRAPH, 2018-08-29)), we disable commit graph within the test script.
--- commit-graph.c | 14 ++++++++++++++ commit-graph.h | 6 ++++++ commit-reach.c | 2 +- t/t6024-recursive-merge.sh | 4 +++- 4 files changed, 24 insertions(+), 2 deletions(-)
commit-graph.c +14 -0
@@ -703,6 +703,20 @@ int generation_numbers_enabled(struct repository *r)return !!first_generation; } +int corrected_commit_dates_enabled (struct repository *r) +{ +struct commit_graph *g; +if (!prepare_commit_graph (r)) +return 0 ; + + g = r->objects->commit_graph; + +if (!g->num_commits) +return 0 ; + +return !!g->chunk_generation_data; +} +static void close_commit_graph_one (struct commit_graph *g) {if (!g)
commit-graph.h +6 -0
@@ -91,6 +91,12 @@ struct commit_graph *parse_commit_graph(void *graph_map, size_t graph_size);*/ int generation_numbers_enabled (struct repository *r); +/* +* Return 1 if and only if the repository has a commit-graph +* file and generation data chunk has been written for the file. +*/ +int corrected_commit_dates_enabled (struct repository *r); +enum commit_graph_write_flags {COMMIT_GRAPH_WRITE_APPEND = (1 <<0 ),COMMIT_GRAPH_WRITE_PROGRESS = (1 <<1 ),
commit-reach.c +1 -1
@@ -39,7 +39,7 @@ static struct commit_list *paint_down_to_common(struct repository *r,int i;timestamp_t last_gen =GENERATION_NUMBER_INFINITY ; -if (!min_generation) +if (!min_generation && !corrected_commit_dates_enabled (r)) queue.compare = compare_commits_by_commit_date; one->object.flags |=PARENT1 ;
t/t6024-recursive-merge.sh +3 -1
@@ -15,6 +15,8 @@ GIT_COMMITTER_DATE="2006-12-12 23:28:00 +0100" export GIT_COMMITTER_DATE test_expect_success 'setup tests' ' + GIT_TEST_COMMIT_GRAPH=0 && + export GIT_TEST_COMMIT_GRAPH && echo 1 >a1 && git add a1 && GIT_AUTHOR_DATE="2006-12-12 23:00:00" git commit -m 1 a1 &&
@@ -66,7 +68,7 @@ test_expect_success 'setup tests' ' ' test_expect_success 'combined merge conflicts' ' - test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git merge -m final G + test_must_fail git merge -m final G ' test_expect_success 'result contains a conflict' '
Signature · 1 line
gitgitgadget
Re: [PATCH v3 10/11] commit-reach: use corrected commit dates in paint_down_to_common()
Hello Abhishek,
Quoted 4 lines · Abhishek Kumar via GitGitGadget
From: Abhishek Kumar <[email protected]>
With corrected commit dates implemented, we no longer have to rely on commit date as a heuristic in
paint_down_to_common().
All right, but it would be nice to have some benchmark data: what were performance when using topological levels, what was performance when using commit date heuristics (before this patch), what is performace now when using corrected commit date.
Quoted 6 lines
t6024-recursive-merge setups a unique repository where all commits have the same committer date without well-defined merge-base. As this has already caused problems (as noted in 859fdc0 (commit-graph: define
GIT_TEST_COMMIT_GRAPH, 2018-08-29)), we disable commit graph within the test script.
OK?
Quoted 9 lines
Signed-off-by Abhishek Kumar <[email protected]>--- commit-graph.c | 14 ++++++++++++++ commit-graph.h | 6 ++++++ commit-reach.c | 2 +- t/t6024-recursive-merge.sh | 4 +++- 4 files changed, 24 insertions(+), 2 deletions(-)
I have reorderd files for easier review.
Quoted 7 lines
commit-graph.h +0
-0@@ -91,6 +91,12 @@ struct commit_graph *parse_commit_graph(void *graph_map, size_t graph_size);*/ int generation_numbers_enabled (struct repository *r);
commit-graph.h struct commit_graph *parse_commit_graph(void *graph_map, size_t graph_size); · line 102
Quoted 10 lines
commit-graph.h +6
-0+/* +* Return 1 if and only if the repository has a commit-graph +* file and generation data chunk has been written for the file. +*/ +int corrected_commit_dates_enabled (struct repository *r); +enum commit_graph_write_flags {COMMIT_GRAPH_WRITE_APPEND = (1 <<0 ),COMMIT_GRAPH_WRITE_PROGRESS = (1 <<1 ),
All right.
Quoted 11 lines
commit-graph.c +3
-0@@ -703,6 +703,20 @@ int generation_numbers_enabled(struct repository *r)return !!first_generation; } +int corrected_commit_dates_enabled (struct repository *r) +{ +struct commit_graph *g;
commit-graph.c int generation_numbers_enabled(struct repository *r) · line 718
Quoted 10 lines
commit-graph.c +10
-0+if (!prepare_commit_graph (r)) +return 0 ; + + g = r->objects->commit_graph; + +if (!g->num_commits) +return 0 ; + +return !!g->chunk_generation_data; +}
The previous commit introduced validate_mixed_generation_chain(), which walked whole split commit-graph chain, and set read_generation_data field in struct commit_graph for all layers in the chain.
This function examines only the top layer, so it follows the assumption that Git would behave in such way that oly topmost layers in the chai can be GDAT-less.
Why the difference? Couldn't validate_mixed_generation_chain() simply call corrected_commit_dates_enabled()?
commit-reach.c static struct commit_list *paint_down_to_common(struct repository *r, · line 42
Quoted 13 lines
commit-graph.c +1
-0+static void close_commit_graph_one (struct commit_graph *g) {if (!g)commit-reach.c +0
-1@@ -39,7 +39,7 @@ static struct commit_list *paint_down_to_common(struct repository *r,int i;timestamp_t last_gen =GENERATION_NUMBER_INFINITY ; -if (!min_generation)
This check was added in 091f4cf (commit: don't use generation numbers if not needed, 2018-08-30) by Derrick Stolee, and its commit message includes benchmark results for running 'git merge-base v4.8 v4.9' in Linux kernel repository:
v2.18.0: 0.122s 167,468 walked
v2.19.0-rc1: 0.547s 635,579 walked
HEAD: 0.127scommit-reach.c static struct commit_list *paint_down_to_common(struct repository *r, · line 43
Quoted 2 lines
commit-reach.c +1
-0+if (!min_generation && !corrected_commit_dates_enabled (r)) queue.compare = compare_commits_by_commit_date;
It would be nice to have similar benchmark for this change... unless of course there is no change in performance, but I think then it needs to be stated explicitly. I think.
Quoted 16 lines
commit-reach.c +0
-0one->object.flags |=PARENT1 ;t/t6024-recursive-merge.sh +2
-0@@ -15,6 +15,8 @@ GIT_COMMITTER_DATE="2006-12-12 23:28:00 +0100" export GIT_COMMITTER_DATE test_expect_success 'setup tests' ' + GIT_TEST_COMMIT_GRAPH=0 && + export GIT_TEST_COMMIT_GRAPH && echo 1 >a1 && git add a1 && GIT_AUTHOR_DATE="2006-12-12 23:00:00" git commit -m 1 a1 &&
t/t6024-recursive-merge.sh test_expect_success 'setup tests' ' · line 74
Quoted 8 lines
t/t6024-recursive-merge.sh +1
-1@@ -66,7 +68,7 @@ test_expect_success 'setup tests' ' ' test_expect_success 'combined merge conflicts' ' - test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git merge -m final G + test_must_fail git merge -m final G ' test_expect_success 'result contains a conflict' '
OK, so instead of disabling commit-graph for this test, now we disable it for the whole script.
Maybe this change should be in a separate patch?
Best,
Signature · 1 line
Jakub Narębski
Re: [PATCH v3 10/11] commit-reach: use corrected commit dates in paint_down_to_common()
Quoted 15 lines · Jakub Narębski
Hello Abhishek,
Quoted 4 lines · Abhishek Kumar via GitGitGadget
From: Abhishek Kumar <[email protected]>
With corrected commit dates implemented, we no longer have to rely on commit date as a heuristic in
paint_down_to_common().All right, but it would be nice to have some benchmark data: what were performance when using topological levels, what was performance when using commit date heuristics (before this patch), what is performace now when using corrected commit date.
Quoted 1 line
Quoted 7 lines
Quoted 5 lines
t6024-recursive-merge setups a unique repository where all commits have the same committer date without well-defined merge-base. As this has already caused problems (as noted in 859fdc0 (commit-graph: define
GIT_TEST_COMMIT_GRAPH, 2018-08-29)), we disable commit graph within the test script.OK?
In hindsight, that is a terrible explanation. Here's what I have revised this to:
With corrected commit dates implemented, we no longer have to rely on commit date as a heuristic in paint_down_to_common().
While using corrected commit dates Git walks nearly the same number of commits as commit date, the process is slower as for each comparision we have to access the commit-slab (for corrected committer date) instead of accessing struct member (for committer date).
For example, the command git merge-base v4.8 v4.9 on the linux repository walks 167468 commits, taking 0.135s for committer date and 167496 commits, taking 0.157s for corrected committer date respectively.
t6404-recursive-merge setups a unique repository where all commits have the same committer date without well-defined merge-base. As this has already caused problems (as noted in 859fdc0 (commit-graph: define GIT_TEST_COMMIT_GRAPH, 2018-08-29)).
While running tests with GIT_TEST_COMMIT_GRAPH unset, we use committer date as a heuristic in paint_down_to_common(). 6404.1 'combined merge conflicts' merges commits in the order:
- Merge C with B to form a intermediate commit.
- Merge the intermediate commit with A.
With GIT_TEST_COMMIT_GRAPH=1, we write a commit-graph and subsequently use the corrected committer date, which changes the order in which commits are merged:
- Merge A with B to form a intermediate commit.
- Merge the intermediate commit with C.
While resulting repositories are equivalent, 6404.4 'virtual trees were processed' fails with GIT_TEST_COMMIT_GRAPH=1 as we are selecting different merge-bases and thus have different object ids for the intermediate commits.
As this has already causes problems (as noted in 859fdc0 (commit-graph:
define GIT_TEST_COMMIT_GRAPH, 2018-08-29)), we disable commit graph within t6404-recursive-merge.
Quoted 59 lines
Quoted 10 lines
Signed-off-by Abhishek Kumar <[email protected]>--- commit-graph.c | 14 ++++++++++++++ commit-graph.h | 6 ++++++ commit-reach.c | 2 +- t/t6024-recursive-merge.sh | 4 +++- 4 files changed, 24 insertions(+), 2 deletions(-)I have reorderd files for easier review.
Quoted 7 lines
commit-graph.h +0
-0@@ -91,6 +91,12 @@ struct commit_graph *parse_commit_graph(void *graph_map, size_t graph_size);*/ int generation_numbers_enabled (struct repository *r);commit-graph.h struct commit_graph *parse_commit_graph(void *graph_map, size_t graph_size); · line 102
Quoted 10 lines
commit-graph.h +6
-0+/* +* Return 1 if and only if the repository has a commit-graph +* file and generation data chunk has been written for the file. +*/ +int corrected_commit_dates_enabled (struct repository *r); +enum commit_graph_write_flags {COMMIT_GRAPH_WRITE_APPEND = (1 <<0 ),COMMIT_GRAPH_WRITE_PROGRESS = (1 <<1 ),All right.
Quoted 11 lines
commit-graph.c +3
-0@@ -703,6 +703,20 @@ int generation_numbers_enabled(struct repository *r)return !!first_generation; } +int corrected_commit_dates_enabled (struct repository *r) +{ +struct commit_graph *g;commit-graph.c int generation_numbers_enabled(struct repository *r) · line 718
Quoted 10 lines
commit-graph.c +10
-0+if (!prepare_commit_graph (r)) +return 0 ; + + g = r->objects->commit_graph; + +if (!g->num_commits) +return 0 ; + +return !!g->chunk_generation_data; +}The previous commit introduced
validate_mixed_generation_chain(), which walked whole split commit-graph chain, and setread_generation_datafield instruct commit_graphfor all layers in the chain.
Quoted 6 lines
This function examines only the top layer, so it follows the assumption that Git would behave in such way that oly topmost layers in the chai can be GDAT-less.
Why the difference? Couldn't
validate_mixed_generation_chain()simply callcorrected_commit_dates_enabled()?
The previous commit didn't need to walk the whole split commit-graph chain. Because of how we are handling writing in a mixed generation data chunk, if a layer has generation data chunk, all layers below it have a generation data chunk as well.
So, there are two cases at hand:
- Topmost layer has generation data chunk, so we know all layers below it has generation data chunk and we can read values from it.
- Topmost layer does not have generation data chunk, so we know we can't read from generation data chunk.
Just checking the topmost layer suffices - modified the previous commit.
Then, this function is more or less the same as g->read_generation_data that is, if we are reading from generation data chunk, we are using corrected commit dates.
Quoted 24 lines
Quoted 10 lines
commit-graph.c +1
-0+static void close_commit_graph_one (struct commit_graph *g) {if (!g)commit-reach.c +0
-0commit-reach.c static struct commit_list *paint_down_to_common(struct repository *r, · line 42
Quoted 4 lines
commit-reach.c +0
-1@@ -39,7 +39,7 @@ static struct commit_list *paint_down_to_common(struct repository *r,int i;timestamp_t last_gen =GENERATION_NUMBER_INFINITY ; -if (!min_generation)This check was added in 091f4cf (commit: don't use generation numbers if not needed, 2018-08-30) by Derrick Stolee, and its commit message includes benchmark results for running 'git merge-base v4.8 v4.9' in Linux kernel repository:
v2.18.0: 0.122s 167,468 walked v2.19.0-rc1: 0.547s 635,579 walked HEAD: 0.127s
Quoted 7 lines
commit-reach.c static struct commit_list *paint_down_to_common(struct repository *r, · line 43
Quoted 2 lines
commit-reach.c +1
-0+if (!min_generation && !corrected_commit_dates_enabled (r)) queue.compare = compare_commits_by_commit_date;It would be nice to have similar benchmark for this change... unless of course there is no change in performance, but I think then it needs to be stated explicitly. I think.
Mentioned in the commit message - we walk (nearly) the same number of commits but take somewhat longer.
Quoted 23 lines
Quoted 23 lines
commit-reach.c +0
-0one->object.flags |=PARENT1 ;t/t6024-recursive-merge.sh +3
-1@@ -15,6 +15,8 @@ GIT_COMMITTER_DATE="2006-12-12 23:28:00 +0100" export GIT_COMMITTER_DATE test_expect_success 'setup tests' ' + GIT_TEST_COMMIT_GRAPH=0 && + export GIT_TEST_COMMIT_GRAPH && echo 1 >a1 && git add a1 && GIT_AUTHOR_DATE="2006-12-12 23:00:00" git commit -m 1 a1 &&@@ -66,7 +68,7 @@ test_expect_success 'setup tests' ' ' test_expect_success 'combined merge conflicts' ' - test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git merge -m final G + test_must_fail git merge -m final G '
Quoted 6 lines
t/t6024-recursive-merge.sh test_expect_success 'setup tests' ' · line 74
Quoted 1 line
t/t6024-recursive-merge.sh +0
-0test_expect_success 'result contains a conflict' 'OK, so instead of disabling commit-graph for this test, now we disable it for the whole script.
Maybe this change should be in a separate patch?
With the explanation in commit message, it's clear to see how using corrected commit dates leads to an (incorrectly) failing test. Does it still make sense to seperate them?
Quoted 4 lines
Best,
Signature · 1 line
Jakub Narębski
Thanks
- Abhishek
Re: [PATCH v3 10/11] commit-reach: use corrected commit dates in paint_down_to_common()
Hello,
Quoted 6 lines · Abhishek Kumar
Quoted 5 lines · Jakub Narębski
Quoted 2 lines · Abhishek Kumar via GitGitGadget
From: Abhishek Kumar <[email protected]>
Quoted 7 lines
Quoted 7 lines
Quoted 2 lines
With corrected commit dates implemented, we no longer have to rely on commit date as a heuristic in
paint_down_to_common().All right, but it would be nice to have some benchmark data: what were performance when using topological levels, what was performance when using commit date heuristics (before this patch), what is performace now when using corrected commit date.
All right, the new proposed commit message has this benchmark data. Thanks.
Quoted 40 lines
Quoted 7 lines
Quoted 5 lines
t6024-recursive-merge setups a unique repository where all commits have the same committer date without well-defined merge-base. As this has already caused problems (as noted in 859fdc0 (commit-graph: define
GIT_TEST_COMMIT_GRAPH, 2018-08-29)), we disable commit graph within the test script.OK?
In hindsight, that is a terrible explanation. Here's what I have revised this to:
With corrected commit dates implemented, we no longer have to rely on commit date as a heuristic in
paint_down_to_common().While using corrected commit dates Git walks nearly the same number of commits as commit date, the process is slower as for each comparision we have to access the commit-slab (for corrected committer date) instead of accessing struct member (for committer date).
For example, the command
git merge-base v4.8 v4.9on the linux repository walks 167468 commits, taking 0.135s for committer date and 167496 commits, taking 0.157s for corrected committer date respectively.t6404-recursive-merge setups a unique repository where all commits have the same committer date without well-defined merge-base. As this has already caused problems (as noted in 859fdc0 (commit-graph: define
GIT_TEST_COMMIT_GRAPH, 2018-08-29)).While running tests with
GIT_TEST_COMMIT_GRAPHunset, we use committer date as a heuristic inpaint_down_to_common(). 6404.1 'combined merge conflicts' merges commits in the order:
- Merge C with B to form a intermediate commit.
- Merge the intermediate commit with A.With GIT_TEST_COMMIT_GRAPH=1, we write a commit-graph and subsequently use the corrected committer date, which changes the order in which commits are merged:
- Merge A with B to form a intermediate commit.
- Merge the intermediate commit with C.
Quoted 8 lines
While resulting repositories are equivalent, 6404.4 'virtual trees were processed' fails with GIT_TEST_COMMIT_GRAPH=1 as we are selecting different merge-bases and thus have different object ids for the intermediate commits.
As this has already causes problems (as noted in 859fdc0 (commit-graph:
defineGIT_TEST_COMMIT_GRAPH, 2018-08-29)), we disable commit graph within t6404-recursive-merge.
Much better. Thanks a lot.
[...]
Quoted 30 lines
Quoted 30 lines
Quoted 11 lines
commit-graph.c +3
-0@@ -703,6 +703,20 @@ int generation_numbers_enabled(struct repository *r)return !!first_generation; } +int corrected_commit_dates_enabled (struct repository *r) +{ +struct commit_graph *g;commit-graph.c int generation_numbers_enabled(struct repository *r) · line 718
Quoted 10 lines
commit-graph.c +10
-0+if (!prepare_commit_graph (r)) +return 0 ; + + g = r->objects->commit_graph; + +if (!g->num_commits) +return 0 ; + +return !!g->chunk_generation_data; +}The previous commit introduced
validate_mixed_generation_chain(), which walked whole split commit-graph chain, and setread_generation_datafield instruct commit_graphfor all layers in the chain.This function examines only the top layer, so it follows the assumption that Git would behave in such way that oly topmost layers in the chai can be GDAT-less.
Quoted 5 lines
Quoted 2 lines
Why the difference? Couldn't
validate_mixed_generation_chain()simply callcorrected_commit_dates_enabled()?The previous commit didn't need to walk the whole split commit-graph chain.
Errr... but validate_mixed_generation_chain() introduced in previous commit in this patch series *does* walk all the layers of the whole split commit-graph chain.
static void validate_mixed_generation_chain(struct repository *r)
{
struct commit_graph *g = r->objects->commit_graph;
int read_generation_data = 1;
while (g) {
if (!g->chunk_generation_data) {
read_generation_data = 0;
break;
}
g = g->base_graph;
}
g = r->objects->commit_graph;
while (g) {
g->read_generation_data = read_generation_data;
g = g->base_graph;
}
}Moreover it "marks up" the whole chain, actually walking it twice.
You wrote somewhere else (possibly after I wrote this post) that this was needed to handle git commit-graph validate, if I remember it correctly.
If it is true, then we need both approaches: the less expensive one (relying on our assumptions) and the more expensive one. But we need to better explain both: why we need more expensive one, why we can use the less expensive onne (how we ensure that the requirements are fulfilled).
Quoted 11 lines
Because of how we are handling writing in a mixed generation data
chunk, if a layer has generation data chunk, all layers below it have a generation data chunk as well.So, there are two cases at hand:
- Topmost layer has generation data chunk, so we know all layers below it has generation data chunk and we can read values from it.
- Topmost layer does not have generation data chunk, so we know we can't read from generation data chunk.
Quoted 5 lines
Just checking the topmost layer suffices - modified the previous commit.
Then, this function is more or less the same as
g->read_generation_datathat is, if we are reading from generation data chunk, we are using corrected commit dates.
All right. That explains how corrected_commit_dates_enabled() works, but not why we need also validate_mixed_generation_chain() that sets g->read_generation_data for every layer in the chain.
Quoted 32 lines
Quoted 24 lines
Quoted 10 lines
commit-graph.c +1
-0+static void close_commit_graph_one (struct commit_graph *g) {if (!g)commit-reach.c +0
-0commit-reach.c static struct commit_list *paint_down_to_common(struct repository *r, · line 42
Quoted 4 lines
commit-reach.c +0
-1@@ -39,7 +39,7 @@ static struct commit_list *paint_down_to_common(struct repository *r,int i;timestamp_t last_gen =GENERATION_NUMBER_INFINITY ; -if (!min_generation)This check was added in 091f4cf (commit: don't use generation numbers if not needed, 2018-08-30) by Derrick Stolee, and its commit message includes benchmark results for running 'git merge-base v4.8 v4.9' in Linux kernel repository:
v2.18.0: 0.122s 167,468 walked v2.19.0-rc1: 0.547s 635,579 walked HEAD: 0.127sQuoted 7 lines
commit-reach.c static struct commit_list *paint_down_to_common(struct repository *r, · line 43
Quoted 2 lines
commit-reach.c +1
-0+if (!min_generation && !corrected_commit_dates_enabled (r)) queue.compare = compare_commits_by_commit_date;It would be nice to have similar benchmark for this change... unless of course there is no change in performance, but I think then it needs to be stated explicitly. I think.
Quoted 2 lines
Mentioned in the commit message - we walk (nearly) the same number of commits but take somewhat longer.
All right, the new proposed commit message has it.
Sidenote: this is outside of the scope of this patch series, but perhaps we should think about bringing the generation field from the commit-slab back as a member of the struct commit; this would need profiling and benchmarking of the typical workload to get amortized performance across many git commands.
Quoted 6 lines
Quoted 6 lines
t/t6024-recursive-merge.sh
Quoted 6 lines
commit-reach.c +0
-0one->object.flags |=PARENT1 ;t/t6024-recursive-merge.sh +0
-0
Note: this might be now t/t6404-recursive-merge.sh -- 6404 ot 6024.
Quoted 22 lines
Quoted 22 lines
Quoted 10 lines
t/t6024-recursive-merge.sh +2
-0@@ -15,6 +15,8 @@ GIT_COMMITTER_DATE="2006-12-12 23:28:00 +0100" export GIT_COMMITTER_DATE test_expect_success 'setup tests' ' + GIT_TEST_COMMIT_GRAPH=0 && + export GIT_TEST_COMMIT_GRAPH && echo 1 >a1 && git add a1 && GIT_AUTHOR_DATE="2006-12-12 23:00:00" git commit -m 1 a1 &&t/t6024-recursive-merge.sh test_expect_success 'setup tests' ' · line 74
Quoted 8 lines
t/t6024-recursive-merge.sh +1
-1@@ -66,7 +68,7 @@ test_expect_success 'setup tests' ' ' test_expect_success 'combined merge conflicts' ' - test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git merge -m final G + test_must_fail git merge -m final G ' test_expect_success 'result contains a conflict' 'OK, so instead of disabling commit-graph for this test, now we disable it for the whole script.
Quoted 5 lines
Quoted 1 line
Maybe this change should be in a separate patch?
With the explanation in commit message, it's clear to see how using corrected commit dates leads to an (incorrectly) failing test. Does it still make sense to seperate them?
No, I think that the new commit message explains why those changes are together.
On the other hand it might be a good idea to add a TODO comment to this test to mark it as fragile (fixing it is certainly out of scope of this patch series, but better have something to remind us about the issue). Perhaps:
# TODO: fragile test, relies on specific resolving of ambiguity
Or something like that. The original commit that added GIT_TEST_COMMIT_GRAPH=0 (for a single test) explained:
There is one test in t6024-recursive-merge.sh that relies on the merge-base algorithm picking one of two ambiguous merge-bases, and the commit-graph feature changes which merge-base is picked.
I'm not sure of we could salvage some of this test as it is now adding env GIT_TEST_COMMIT_GRAPH=0 in more individual tests instead of turning it off for the whole test script. But that is something that we can do later.
Best,
Signature · 1 line
Jakub Narębski
PATCH v3 11/11 doc: add corrected commit date info
In reply to Abhishek Kumar via GitGitGadgetFrom: Abhishek Kumar <[email protected]>
With generation data chunk and corrected commit dates implemented, let's update the technical documentation for commit-graph.
--- .../technical/commit-graph-format.txt | 12 ++--- Documentation/technical/commit-graph.txt | 45 ++++++++++++------- 2 files changed, 36 insertions(+), 21 deletions(-)
Documentation/technical/commit-graph-format.txt +7 -5
@@ -4,11 +4,7 @@ Git commit graph format The Git commit graph stores a list of commit OIDs and some associated metadata, including: -- The generation number of the commit. Commits with no parents have - generation number 1; commits with parents have generation number - one more than the maximum generation number of its parents. We - reserve zero as special, and can be used to mark a generation - number invalid or as "not computed". +- The generation number of the commit. - The root tree OID.
@@ -88,6 +84,12 @@ CHUNK DATA: 2 bits of the lowest byte, storing the 33rd and 34th bit of the commit time. + Generation Data (ID: {'G', 'D', 'A', 'T' }) (N * 4 bytes) [Optional] + * This list of 4-byte values store corrected commit date offsets for the + commits, arranged in the same order as commit data chunk. + * This list can be later modified to store future generation number related + data. + Extra Edge List (ID: {'E', 'D', 'G', 'E'}) [Optional] This list of 4-byte values store the second through nth parents for all octopus merges. The second parent value in the commit data stores
Documentation/technical/commit-graph.txt +29 -16
78 lines not on this page · raw message
Signature · 1 line
gitgitgadget
Re: [PATCH v3 11/11] doc: add corrected commit date info
Hello,
Quoted 6 lines · Abhishek Kumar via GitGitGadget
From: Abhishek Kumar <[email protected]>
With generation data chunk and corrected commit dates implemented, let's update the technical documentation for commit-graph.
Signed-off-by Abhishek Kumar <[email protected]>
All right.
Quoted 10 lines
--- .../technical/commit-graph-format.txt | 12 ++--- Documentation/technical/commit-graph.txt | 45 ++++++++++++------- 2 files changed, 36 insertions(+), 21 deletions(-)Documentation/technical/commit-graph-format.txt +0
-0
Documentation/technical/commit-graph-format.txt Git commit graph format · line 7
Quoted 9 lines
Documentation/technical/commit-graph-format.txt +1
-5@@ -4,11 +4,7 @@ Git commit graph format The Git commit graph stores a list of commit OIDs and some associated metadata, including: -- The generation number of the commit. Commits with no parents have - generation number 1; commits with parents have generation number - one more than the maximum generation number of its parents. We - reserve zero as special, and can be used to mark a generation - number invalid or as "not computed". +- The generation number of the commit.
All right, that was duplicated information. Now that we need to talk about two of them, it would not make sense to duplicate that.
Documentation/technical/commit-graph-format.txt CHUNK DATA:
Quoted 4 lines
Documentation/technical/commit-graph-format.txt +0
-0- The root tree OID.@@ -88,6 +84,12 @@ CHUNK DATA:
Shouldn't we also replace 'generation number' occurences in description of the Commit Data (CDAT) chunk with either 'topological level' or 'generation number v1'?
Documentation/technical/commit-graph-format.txt CHUNK DATA: · line 87
Quoted 4 lines
Documentation/technical/commit-graph-format.txt +1
-02 bits of the lowest byte, storing the 33rd and 34th bit of the commit time. + Generation Data (ID: {'G', 'D', 'A', 'T' }) (N * 4 bytes) [Optional]
It is not exactly 'optional', as it implies that we need to turn it on (or that we can turn it off). It is more 'conditional', as it can be not present due to outside influences (mixed-version environment).
Documentation/technical/commit-graph-format.txt CHUNK DATA: · line 89
Quoted 2 lines
Documentation/technical/commit-graph-format.txt +2
-0+ * This list of 4-byte values store corrected commit date offsets for the + commits, arranged in the same order as commit data chunk.
I have just realized purely theoretical, but possible, problem with storing non-monotinic generation number related values like corrected commit date offset in constrained space. There are problems with clamping them.
Say that somewhere in the ancestry chain there is a commit A with commit date far in the future by mistake, for example 2120-08-22; it is important for that date to be not able to be represented using uint32_t. Say that a later descendant commit B is malformed, and has committer date of 0, that is 1970-01-01. This means that the corrected commit date for B must be larger than 2120-08-22 - which for this commit means that corrected commit date offset do not fit in 32 bits, and must be clamped (replaced) with GENERATION_NUMBER_V2_OFFSET_MAX.
Say that we have commit C that is child of B, and it has correct commit date. Because of mistake in commit A, it has corrected commit date of more than 2120-08-22 (corrected commit date degenerated into topological level plus constant).
Now C can reach B, and B can reach A. However, if we recover corrected commit date of B out of its date=0 and offset=GENERATION_NUMBER_V2_OFFSET_MAX we get a number that is smaller than correct corrected commit date. We will have
gen(A) > date(B) + offset(B) < gen(C)
Which breaks reachability condition guarantee.
If instead we use GENERATION_NUMBER_V2_MAX for commits with clamped corrected commit date, that is offset=GENERATION_NUMBER_V2_OFFSET_MAX, we would get
gen(A) < GENERATION_NUMBER_V2_MAX > gen(C)
And again reachability condition is broken.
This is a very contrived but possible example. This shouldn't happen, but ufortunately it can happen.
The question is how to deal with this issue. Ignore it as unlikely? Switch to storing corrected commit date, which is monotonic, so if there is commit with GENERATION_NUMBER_V2_MAX, then subsequent descendant commits will also have GENERATION_NUMBER_V2_MAX -- and pay with up to 7% larger commit-graph file?
Documentation/technical/commit-graph-format.txt CHUNK DATA: · line 91
Quoted 2 lines
Documentation/technical/commit-graph-format.txt +2
-0+ * This list can be later modified to store future generation number related + data.
How can it be later modified? There is no header, no version number. How would we add another generation number data?
Quoted 9 lines
Documentation/technical/commit-graph-format.txt +1
-0+ Extra Edge List (ID: {'E', 'D', 'G', 'E'}) [Optional] This list of 4-byte values store the second through nth parents for all octopus merges. The second parent value in the commit data storesDocumentation/technical/commit-graph.txt +0
-0
Documentation/technical/commit-graph.txt A consumer may load the following info for a commit from the graph: · line 43
Quoted 7 lines
Documentation/technical/commit-graph.txt +3
-1@@ -38,14 +38,27 @@ A consumer may load the following info for a commit from the graph: Values 1-4 satisfy the requirements of parse_commit_gently(). -Define the "generation number" of a commit recursively as follows: +There are two definitions of generation number: +1. Corrected committer dates +2. Topological levels
Should we add versioning info, that is:
+1. Corrected committer dates (generation number v2) +2. Topological levels (generation number v1)
Documentation/technical/commit-graph.txt A consumer may load the following info for a commit from the graph: · line 56
Quoted 13 lines
Documentation/technical/commit-graph.txt +11
-0+ +Define "corrected committer date" of a commit recursively as follows: + + * A commit with no parents (a root commit) has corrected committer date + equal to its committer date. + + * A commit with at least one parent has corrected committer date equal to + the maximum of its commiter date and one more than the largest corrected + committer date among its parents. + +Define the "topological level" of a commit recursively as follows: * A commit with no parents (a root commit) has generation number one.
Shouldn't this be
* A commit with no parents (a root commit) has topological level of one.
Documentation/technical/commit-graph.txt A consumer may load the following info for a commit from the graph: · line 63
Quoted 10 lines
Documentation/technical/commit-graph.txt +3
-3- * A commit with at least one parent has generation number one more than - the largest generation number among its parents. + * A commit with at least one parent has topological level one more than + the largest topological level among its parents. -Equivalently, the generation number of a commit A is one more than the +Equivalently, the topological level of a commit A is one more than the length of a longest path from A to a root commit. The recursive definition is easier to use for computation and observing the following property:
We should probably explicitly state that the property state applies to both versions of generation number, not only to topological level.
Documentation/technical/commit-graph.txt numbers, the general heuristic is the following: · line 74
Quoted 10 lines
Documentation/technical/commit-graph.txt +0
-5@@ -67,17 +80,12 @@ numbers, the general heuristic is the following: If A and B are commits with commit time X and Y, respectively, and X < Y, then A _probably_ cannot reach B. -This heuristic is currently used whenever the computation is allowed to -violate topological relationships due to clock skew (such as "git log" -with default order), but is not used when the topological order is -required (such as merge base calculations, "git log --graph"). -
To be overly pedantic, this heuristic is still used, but now in much more rare case. In addition to what is stated above, at least one layer in the split commit-graph chain must have been generated by "Old" Git, for the date heuristic to be used.
But that might be unnecessary level of detail.
Documentation/technical/commit-graph.txt numbers, the general heuristic is the following: · line 88
Quoted 7 lines
Documentation/technical/commit-graph.txt +1
-1In practice, we expect some commits to be created recently and not stored in the commit graph. We can treat these commits as having "infinite" generation number and walk until reaching commits with known generation number. -We use the macro GENERATION_NUMBER_INFINITY = 0xFFFFFFFF to mark commits not +We use the macro GENERATION_NUMBER_INFINITY to mark commits not
All right.
Quoted 8 lines
Documentation/technical/commit-graph.txt +0
-1in the commit-graph file. If a commit-graph file was written by a version of Git that did not compute generation numbers, then those commits will have generation number represented by the macro GENERATION_NUMBER_ZERO = 0.@@ -93,12 +101,11 @@ fully-computed generation numbers. Using strict inequality may result in walking a few extra commits, but the simplicity in dealing with commits with generation number *_INFINITY or *_ZERO is valuable. -We use the macro GENERATION_NUMBER_MAX = 0x3FFFFFFF to for commits whose
Documentation/technical/commit-graph.txt fully-computed generation numbers. Using strict inequality may result in · line 108
Quoted 10 lines
Documentation/technical/commit-graph.txt +5
-5-generation numbers are computed to be at least this value. We limit at -this value since it is the largest value that can be stored in the -commit-graph file using the 30 bits available to generation numbers. This -presents another case where a commit can have generation number equal to -that of a parent. +We use the macro GENERATION_NUMBER_MAX for commits whose generation numbers +are computed to be at least this value. We limit at this value since it is +the largest value that can be stored in the commit-graph file using the +available to generation numbers. This presents another case where a +commit can have generation number equal to that of a parent.
All right, though it could have been done without re-wrapping, so that only first line would be marked as changed.
As I wrote, there is theoretical problem with this for offsets.
Documentation/technical/commit-graph.txt The merge strategy values (2 for the size multiple, 64,000 for the maximum · line 282
Quoted 13 lines
Documentation/technical/commit-graph.txt +6
-0Design Details --------------@@ -267,6 +274,12 @@ The merge strategy values (2 for the size multiple, 64,000 for the maximum number of commits) could be extracted into config settings for full flexibility. +We also merge commit-graph chains when we try to write a commit graph with +two different generation number definitions as they cannot be compared directly. +We overwrite the existing chain and create a commit-graph with the newer or more +efficient defintion. For example, overwriting topological levels commit graph +chain to create a corrected commit dates commit graph chain. +
This is more complicated than that.
I think we should explicitly state that Git ensures that in split commit-graph chain, if there are layers without the GDAT chunk (that force Git to use topological levels for generation numbers), then they are top layers. So if there is commit-graph file created by "Old" Git, then when addig new layer it would also be GDAT-less.
Now how to write this...
Documentation/technical/commit-graph.txt The merge strategy values (2 for the size multiple, 64,000 for the maximum · line 285
Quoted 3 lines
Documentation/technical/commit-graph.txt +0
-0## Deleting graph-{hash} files After a new tip file is written, some `graph-{hash}` files may no longer
Best,
Signature · 1 line
Jakub Narębski
Re: [PATCH v3 11/11] doc: add corrected commit date info
Quoted 37 lines · Jakub Narębski
Hello,
Quoted 6 lines · Abhishek Kumar via GitGitGadget
From: Abhishek Kumar <[email protected]>
With generation data chunk and corrected commit dates implemented, let's update the technical documentation for commit-graph.
Signed-off-by Abhishek Kumar <[email protected]>All right.
Quoted 10 lines
--- .../technical/commit-graph-format.txt | 12 ++--- Documentation/technical/commit-graph.txt | 45 ++++++++++++------- 2 files changed, 36 insertions(+), 21 deletions(-)Documentation/technical/commit-graph-format.txt +0
-0Documentation/technical/commit-graph-format.txt Git commit graph format · line 7
Quoted 9 lines
Documentation/technical/commit-graph-format.txt +1
-5@@ -4,11 +4,7 @@ Git commit graph format The Git commit graph stores a list of commit OIDs and some associated metadata, including: -- The generation number of the commit. Commits with no parents have - generation number 1; commits with parents have generation number - one more than the maximum generation number of its parents. We - reserve zero as special, and can be used to mark a generation - number invalid or as "not computed". +- The generation number of the commit.All right, that was duplicated information. Now that we need to talk about two of them, it would not make sense to duplicate that.
Quoted 1 line
Quoted 7 lines
Documentation/technical/commit-graph-format.txt CHUNK DATA:
Quoted 3 lines
- The root tree OID.
Documentation/technical/commit-graph-format.txt +0
-0@@ -88,6 +84,12 @@ CHUNK DATA:Shouldn't we also replace 'generation number' occurences in description of the Commit Data (CDAT) chunk with either 'topological level' or 'generation number v1'?
Yes, we should.
Quoted 45 lines
Documentation/technical/commit-graph-format.txt CHUNK DATA: · line 87
Quoted 5 lines
Documentation/technical/commit-graph-format.txt +1
-02 bits of the lowest byte, storing the 33rd and 34th bit of the commit time. + Generation Data (ID: {'G', 'D', 'A', 'T' }) (N * 4 bytes) [Optional]It is not exactly 'optional', as it implies that we need to turn it on (or that we can turn it off). It is more 'conditional', as it can be not present due to outside influences (mixed-version environment).
Documentation/technical/commit-graph-format.txt CHUNK DATA: · line 89
Quoted 2 lines
Documentation/technical/commit-graph-format.txt +2
-0+ * This list of 4-byte values store corrected commit date offsets for the + commits, arranged in the same order as commit data chunk.I have just realized purely theoretical, but possible, problem with storing non-monotinic generation number related values like corrected commit date offset in constrained space. There are problems with clamping them.
Say that somewhere in the ancestry chain there is a commit A with commit date far in the future by mistake, for example 2120-08-22; it is important for that date to be not able to be represented using
uint32_t. Say that a later descendant commit B is malformed, and has committer date of 0, that is 1970-01-01. This means that the corrected commit date for B must be larger than 2120-08-22 - which for this commit means that corrected commit date offset do not fit in 32 bits, and must be clamped (replaced) withGENERATION_NUMBER_V2_OFFSET_MAX.Say that we have commit C that is child of B, and it has correct commit date. Because of mistake in commit A, it has corrected commit date of more than 2120-08-22 (corrected commit date degenerated into topological level plus constant).
Now C can reach B, and B can reach A. However, if we recover corrected commit date of B out of its date=0 and offset=GENERATION_NUMBER_V2_OFFSET_MAX we get a number that is smaller than correct corrected commit date. We will have
gen(A) > date(B) + offset(B) < gen(C)Which breaks reachability condition guarantee.
If instead we use
GENERATION_NUMBER_V2_MAXfor commits with clamped corrected commit date, that is offset=GENERATION_NUMBER_V2_OFFSET_MAX, we would get
Quoted 7 lines
gen(A) <
GENERATION_NUMBER_V2_MAX> gen(C)And again reachability condition is broken.
This is a very contrived but possible example. This shouldn't happen, but ufortunately it can happen.
Yes, that's very unfortunate.
Here's a much simpler example:
A commit P has an reasonable commit date (i.e. after release of Git to present) D and has a child commit C with committer date 0. Now, the corrected commiter date of C would D + 1 and the offset would be same too, as the committer date is zero. This overflows as reasonable dates are of the order 2 ^ 34.
Quoted 7 lines
The question is how to deal with this issue. Ignore it as unlikely? Switch to storing corrected commit date, which is monotonic, so if there is commit with
GENERATION_NUMBER_V2_MAX, then subsequent descendant commits will also haveGENERATION_NUMBER_V2_MAX-- and pay with up to 7% larger commit-graph file?
To be honest, I would prefer storing corrected committer dates over storing offsets.
While it is 7% of the size of commit-graph file, it is also *only* around ~3.5 MB for a repository of the size of linux kernel (and IIRC correctly, the Windows repo has ~2M commits, it amounts to ~8 MB).
Minimizing space and memory requirements are a top priority, but shouldn't making sure our program is correct and efficient to be a greater priority?
I would love to hear your and Dr. Stolee's opinions on this.
Quoted 6 lines
Documentation/technical/commit-graph-format.txt CHUNK DATA: · line 91
Quoted 2 lines
Documentation/technical/commit-graph-format.txt +2
-0+ * This list can be later modified to store future generation number related + data.How can it be later modified? There is no header, no version number. How would we add another generation number data?
We could modify the graph version in future. Here's how I think it would work:
Graph Version 1, No GDAT -> Topological level
Graph Version 2, GDAT -> Corrected committer dates
Graph Version 3, GDAT -> Generation number v3
and so on.
Of course, we do not have to update generation number definition for each graph version.
However, my statement could still be wrong for things that we do not foresee (similar to how we missed the hard die on different graph version), so I am removing the statement.
Quoted 19 lines
Quoted 9 lines
Documentation/technical/commit-graph-format.txt +1
-0+ Extra Edge List (ID: {'E', 'D', 'G', 'E'}) [Optional] This list of 4-byte values store the second through nth parents for all octopus merges. The second parent value in the commit data storesDocumentation/technical/commit-graph.txt +0
-0Documentation/technical/commit-graph.txt A consumer may load the following info for a commit from the graph: · line 43
Quoted 7 lines
Documentation/technical/commit-graph.txt +3
-1@@ -38,14 +38,27 @@ A consumer may load the following info for a commit from the graph: Values 1-4 satisfy the requirements of parse_commit_gently(). -Define the "generation number" of a commit recursively as follows: +There are two definitions of generation number: +1. Corrected committer dates +2. Topological levelsShould we add versioning info, that is:
Quoted 3 lines
+1. Corrected committer dates (generation number v2) +2. Topological levels (generation number v1)
Yes, added.
Quoted 12 lines
Quoted 12 lines
Documentation/technical/commit-graph.txt +11
-0+ +Define "corrected committer date" of a commit recursively as follows: + + * A commit with no parents (a root commit) has corrected committer date + equal to its committer date. + + * A commit with at least one parent has corrected committer date equal to + the maximum of its commiter date and one more than the largest corrected + committer date among its parents. + +Define the "topological level" of a commit recursively as follows:
Quoted 6 lines
Documentation/technical/commit-graph.txt A consumer may load the following info for a commit from the graph: · line 56
Quoted 1 line
Documentation/technical/commit-graph.txt +0
-0* A commit with no parents (a root commit) has generation number one.Shouldn't this be
* A commit with no parents (a root commit) has topological level of one.
Thanks, fixed!
Quoted 82 lines
Documentation/technical/commit-graph.txt A consumer may load the following info for a commit from the graph: · line 63
Quoted 10 lines
Documentation/technical/commit-graph.txt +3
-3- * A commit with at least one parent has generation number one more than - the largest generation number among its parents. + * A commit with at least one parent has topological level one more than + the largest topological level among its parents. -Equivalently, the generation number of a commit A is one more than the +Equivalently, the topological level of a commit A is one more than the length of a longest path from A to a root commit. The recursive definition is easier to use for computation and observing the following property:We should probably explicitly state that the property state applies to both versions of generation number, not only to topological level.
Documentation/technical/commit-graph.txt numbers, the general heuristic is the following: · line 74
Quoted 10 lines
Documentation/technical/commit-graph.txt +0
-5@@ -67,17 +80,12 @@ numbers, the general heuristic is the following: If A and B are commits with commit time X and Y, respectively, and X < Y, then A _probably_ cannot reach B. -This heuristic is currently used whenever the computation is allowed to -violate topological relationships due to clock skew (such as "git log" -with default order), but is not used when the topological order is -required (such as merge base calculations, "git log --graph"). -To be overly pedantic, this heuristic is still used, but now in much more rare case. In addition to what is stated above, at least one layer in the split commit-graph chain must have been generated by "Old" Git, for the date heuristic to be used.
But that might be unnecessary level of detail.
Documentation/technical/commit-graph.txt numbers, the general heuristic is the following: · line 88
Quoted 7 lines
Documentation/technical/commit-graph.txt +1
-1In practice, we expect some commits to be created recently and not stored in the commit graph. We can treat these commits as having "infinite" generation number and walk until reaching commits with known generation number. -We use the macro GENERATION_NUMBER_INFINITY = 0xFFFFFFFF to mark commits not +We use the macro GENERATION_NUMBER_INFINITY to mark commits notAll right.
Quoted 8 lines
Documentation/technical/commit-graph.txt +0
-1in the commit-graph file. If a commit-graph file was written by a version of Git that did not compute generation numbers, then those commits will have generation number represented by the macro GENERATION_NUMBER_ZERO = 0.@@ -93,12 +101,11 @@ fully-computed generation numbers. Using strict inequality may result in walking a few extra commits, but the simplicity in dealing with commits with generation number *_INFINITY or *_ZERO is valuable. -We use the macro GENERATION_NUMBER_MAX = 0x3FFFFFFF to for commits whoseDocumentation/technical/commit-graph.txt fully-computed generation numbers. Using strict inequality may result in · line 108
Quoted 10 lines
Documentation/technical/commit-graph.txt +5
-5-generation numbers are computed to be at least this value. We limit at -this value since it is the largest value that can be stored in the -commit-graph file using the 30 bits available to generation numbers. This -presents another case where a commit can have generation number equal to -that of a parent. +We use the macro GENERATION_NUMBER_MAX for commits whose generation numbers +are computed to be at least this value. We limit at this value since it is +the largest value that can be stored in the commit-graph file using the +available to generation numbers. This presents another case where a +commit can have generation number equal to that of a parent.All right, though it could have been done without re-wrapping, so that only first line would be marked as changed.
As I wrote, there is theoretical problem with this for offsets.
Documentation/technical/commit-graph.txt The merge strategy values (2 for the size multiple, 64,000 for the maximum · line 282
Quoted 13 lines
Documentation/technical/commit-graph.txt +6
-0Design Details --------------@@ -267,6 +274,12 @@ The merge strategy values (2 for the size multiple, 64,000 for the maximum number of commits) could be extracted into config settings for full flexibility. +We also merge commit-graph chains when we try to write a commit graph with +two different generation number definitions as they cannot be compared directly. +We overwrite the existing chain and create a commit-graph with the newer or more +efficient defintion. For example, overwriting topological levels commit graph +chain to create a corrected commit dates commit graph chain. +This is more complicated than that.
Quoted 7 lines
I think we should explicitly state that Git ensures that in split commit-graph chain, if there are layers without the GDAT chunk (that force Git to use topological levels for generation numbers), then they are top layers. So if there is commit-graph file created by "Old" Git, then when addig new layer it would also be GDAT-less.
Now how to write this...
Thinking about this, I feel creating a new section called "Handling Mixed Generation Number Chains" made more sense:
## Handling Mixed Generation Number Chains
With the introduction of generation number v2 and generation data chunk, the following scenario is possible:
1. "New" Git writes a commit-graph with a GDAT chunk.
2. "Old" Git writes a split commit-graph on top without a GDAT chunk.
The commits in the lower layer will be interpreted as having very large generation values (commit date plus offset) compared to the generation numbers in the top layer (toplogical level). This violates the expectation that the generation of a parent is strictly smaller than the generation of a child. In such cases, we revert to using topological levels for all layers to maintain backwards compatability.
When writing a new layer in split commit-graph, we write a GDAT chunk only if the topmost layer has a GDAT chunk. This guarantees that if a lyer has GDAT chunk, all lower layers must have a GDAT chunk as well.
Rewriting layers follows similar approach: if the topmost layer below set of layers being rewriteen (in the split commit-graph chain) exists, and it does not contain GDAT chunk, then the result of rewrite does not have GDAT chunks either.
Quoted 8 lines
Documentation/technical/commit-graph.txt The merge strategy values (2 for the size multiple, 64,000 for the maximum · line 285
Quoted 4 lines
Documentation/technical/commit-graph.txt +0
-0## Deleting graph-{hash} files After a new tip file is written, some `graph-{hash}` files may no longerBest,
Signature · 1 line
Jakub Narębski
Thanks
- Abhishek
Re: [PATCH v3 11/11] doc: add corrected commit date info
Hello,
Quoted 2 lines · Abhishek Kumar
Quoted 1 line · Jakub Narębski
"Abhishek Kumar via GitGitGadget" <[email protected]> writes:
[...]
Quoted 45 lines
Quoted 35 lines
Documentation/technical/commit-graph-format.txt CHUNK DATA: · line 89
Quoted 2 lines
Documentation/technical/commit-graph-format.txt +2
-0+ * This list of 4-byte values store corrected commit date offsets for the + commits, arranged in the same order as commit data chunk.I have just realized purely theoretical, but possible, problem with storing non-monotinic generation number related values like corrected commit date offset in constrained space. There are problems with clamping them.
Say that somewhere in the ancestry chain there is a commit A with commit date far in the future by mistake, for example 2120-08-22; it is important for that date to be not able to be represented using
uint32_t. Say that a later descendant commit B is malformed, and has committer date of 0, that is 1970-01-01. This means that the corrected commit date for B must be larger than 2120-08-22 - which for this commit means that corrected commit date offset do not fit in 32 bits, and must be clamped (replaced) withGENERATION_NUMBER_V2_OFFSET_MAX.Say that we have commit C that is child of B, and it has correct commit date. Because of mistake in commit A, it has corrected commit date of more than 2120-08-22 (corrected commit date degenerated into topological level plus constant).
Now C can reach B, and B can reach A. However, if we recover corrected commit date of B out of its date=0 and offset=GENERATION_NUMBER_V2_OFFSET_MAX we get a number that is smaller than correct corrected commit date. We will have
gen(A) > date(B) + offset(B) < gen(C)Which breaks reachability condition guarantee.
If instead we use
GENERATION_NUMBER_V2_MAXfor commits with clamped corrected commit date, that is offset=GENERATION_NUMBER_V2_OFFSET_MAX, we would getQuoted 7 lines
gen(A) <
GENERATION_NUMBER_V2_MAX> gen(C)And again reachability condition is broken.
This is a very contrived but possible example. This shouldn't happen, but ufortunately it can happen.
Yes, that's very unfortunate.
Quoted 7 lines
Here's a much simpler example:
A commit P has an reasonable commit date (i.e. after release of Git to present) D and has a child commit C with committer date 0. Now, the corrected commiter date of C would D + 1 and the offset would be same too, as the committer date is zero. This overflows as reasonable dates are of the order 2 ^ 34.
No, we need the value of date D that doesn't fit in 2^32 _unsigned_ value, so it needs to be even more in the future than Y2k38 (2038-01-19 03:14:07), which is related to storing date as a _signed_ 32-bit integer
The current-ish Unix epoch time is 1598524281 - let's use it for value of D. Then the offset for commit C would be 1598524282. The current proposal uses 32 bits to store commit date offsets (as unsigned value). The maximum value of offset that we can store is therefore 2^32 - 1, which is 4294967295.
corrected commit date offset(C) = 1,598,524,282
GENERATION_NUMBER_V2_MAX = 4,294,967,295
As you can see there is no overflow in the simplified example.
Quoted 8 lines
Quoted 7 lines
The question is how to deal with this issue. Ignore it as unlikely? Switch to storing corrected commit date, which is monotonic, so if there is commit with
GENERATION_NUMBER_V2_MAX, then subsequent descendant commits will also haveGENERATION_NUMBER_V2_MAX-- and pay with up to 7% larger commit-graph file?
Quoted 6 lines
To be honest, I would prefer storing corrected committer dates over storing offsets.
While it is 7% of the size of commit-graph file, it is also *only* around ~3.5 MB for a repository of the size of linux kernel (and IIRC correctly, the Windows repo has ~2M commits, it amounts to ~8 MB).
It is up to 7% of per-commit data, and it doesn't take into account EDGE chunk (for octopus merges), and it doesn't also take into account the size of changed-paths Bloom filters data take in the commit-graph.
Quoted 3 lines
Minimizing space and memory requirements are a top priority, but shouldn't making sure our program is correct and efficient to be a greater priority?
On the other hand the case where we would encounter offsets that do not fit in uint32_t is extremply unlikely in sane repositories.
I can think of three solutions:
1. use 64-bit corrected commit dates
- advantages:
* simplest code,
* no need for overflow handling, as we can store all possible values of timestamp_t
- disadvantages:
* commit-graph size increased by up to 7%
2. use 32-bit corrected commit date offsets,
but simply do not store GDAT chunk if there is offset that would not fit in 32-bit wide field
- advantages:
* commit-graph is smaller
* relatively simple overflow handling
- disadvantages:
* performance penalty (generation number v1 vs v2) for abnormal repositories (with overflow not fitting in uint32_t)
* tests would be needed to exercise the overflow code
3. use 32-bit for corrected commit date offset,
with oveflow handling, for example using most significant bit to denote that other bits store position into offset overflow with 64-bits for those offsets that do not fit in 31-bits
- advantages:
* commit-graph is smaller, increasing for abnormal repos
- disadvantages:
* most complex code of all proposed solutions
* smaller overflow limit of 2^31 - 1
* tests would be needed to exercise the overflow code
I think because the situation where we encounter overflow in 32-bit corrected commit date offset is rare, we should go with either 1 or 2 solution.
Quoted 1 line
I would love to hear your and Dr. Stolee's opinions on this.
I have CC-ed Junio C Hamano to ask for his opinion.
Quoted 10 lines
Quoted 6 lines
Documentation/technical/commit-graph-format.txt CHUNK DATA: · line 91
Quoted 2 lines
Documentation/technical/commit-graph-format.txt +2
-0+ * This list can be later modified to store future generation number related + data.How can it be later modified? There is no header, no version number. How would we add another generation number data?
We could modify the graph version in future. Here's how I think it would work:
Quoted 8 lines
Graph Version 1, No GDAT -> Topological level
Graph Version 2, GDAT -> Corrected committer dates
Graph Version 3, GDAT -> Generation number v3and so on.
Of course, we do not have to update generation number definition for each graph version.
So it was about generic mechanism, not something specific to the GDAT chunk.
Quoted 3 lines
However, my statement could still be wrong for things that we do not foresee (similar to how we missed the hard die on different graph version), so I am removing the statement.
Good.
[...]
Quoted 35 lines
Quoted 9 lines
Documentation/technical/commit-graph.txt The merge strategy values (2 for the size multiple, 64,000 for the maximum · line 282
Quoted 6 lines
Documentation/technical/commit-graph.txt +6
-0+We also merge commit-graph chains when we try to write a commit graph with +two different generation number definitions as they cannot be compared directly. +We overwrite the existing chain and create a commit-graph with the newer or more +efficient defintion. For example, overwriting topological levels commit graph +chain to create a corrected commit dates commit graph chain. +This is more complicated than that.
Quoted 7 lines
I think we should explicitly state that Git ensures that in split commit-graph chain, if there are layers without the GDAT chunk (that force Git to use topological levels for generation numbers), then they are top layers. So if there is commit-graph file created by "Old" Git, then when addig new layer it would also be GDAT-less.
Now how to write this...
Thinking about this, I feel creating a new section called "Handling Mixed Generation Number Chains" made more sense:
## Handling Mixed Generation Number Chains
With the introduction of generation number v2 and generation data chunk, the following scenario is possible:
1. "New" Git writes a commit-graph with a GDAT chunk.
2. "Old" Git writes a split commit-graph on top without a GDAT chunk.The commits in the lower layer will be interpreted as having very large generation values (commit date plus offset) compared to the generation numbers in the top layer (toplogical level). This violates the expectation that the generation of a parent is strictly smaller than the generation of a child. In such cases, we revert to using topological levels for all layers to maintain backwards compatability.
Quoted 8 lines
When writing a new layer in split commit-graph, we write a GDAT chunk only if the topmost layer has a GDAT chunk. This guarantees that if a lyer has GDAT chunk, all lower layers must have a GDAT chunk as well.
Rewriting layers follows similar approach: if the topmost layer below set of layers being rewriteen (in the split commit-graph chain) exists, and it does not contain GDAT chunk, then the result of rewrite does not have GDAT chunks either.
Good idea, and nice writeup.
Best,
Signature · 1 line
Jakub Narębski
Re: [PATCH v3 11/11] doc: add corrected commit date info
In reply to Abhishek KumarQuoted 10 lines · Abhishek Kumar
Thinking about this, I feel creating a new section called "Handling Mixed Generation Number Chains" made more sense:
## Handling Mixed Generation Number Chains
With the introduction of generation number v2 and generation data chunk, the following scenario is possible:
1. "New" Git writes a commit-graph with a GDAT chunk.
2. "Old" Git writes a split commit-graph on top without a GDAT chunk.
I like the idea of this section, and this setup is good.
Quoted 6 lines
The commits in the lower layer will be interpreted as having very large generation values (commit date plus offset) compared to the generation numbers in the top layer (toplogical level). This violates the expectation that the generation of a parent is strictly smaller than the generation of a child. In such cases, we revert to using topological levels for all layers to maintain backwards compatability.
s/toplogical/topological
But also, we don't want to phrase this as "in this case, we do the wrong thing" but instead
A naive approach of using the newest available generation number from each layer would lead to violated expectations: the lower layer would use corrected commit dates which are much larger than the topological levels of the higher layer. For this reason, Git inspects each layer to see if any layer is missing corrected commit dates. In such a case, Git only uses topological levels.
Quoted 3 lines
When writing a new layer in split commit-graph, we write a GDAT chunk only if the topmost layer has a GDAT chunk. This guarantees that if a lyer has GDAT chunk, all lower layers must have a GDAT chunk as well.
s/lyer/layer
Perhaps leaving this at a higher level than referencing "GDAT chunk" is advisable. Perhaps use "we write corrected commit dates" or "all lower layers must store corrected commit dates as well", for example.
Quoted 4 lines
Rewriting layers follows similar approach: if the topmost layer below set of layers being rewriteen (in the split commit-graph chain) exists, and it does not contain GDAT chunk, then the result of rewrite does not have GDAT chunks either.
This could use more positive language to make it clear that sometimes we _do_ want to write corrected commit dates when merging layers:
When merging layers, we do not consider whether the merged layers had corrected commit dates. Instead, the new layer will have corrected commit dates if and only if all existing layers below the new layer have corrected commit dates.
Thanks,
-Stolee
Re: [PATCH v3 11/11] doc: add corrected commit date info
Quoted 48 lines · Derrick Stolee
Quoted 10 lines · Abhishek Kumar
Thinking about this, I feel creating a new section called "Handling Mixed Generation Number Chains" made more sense:
## Handling Mixed Generation Number Chains
With the introduction of generation number v2 and generation data chunk, the following scenario is possible:
1. "New" Git writes a commit-graph with a GDAT chunk.
2. "Old" Git writes a split commit-graph on top without a GDAT chunk.I like the idea of this section, and this setup is good.
Quoted 6 lines
The commits in the lower layer will be interpreted as having very large generation values (commit date plus offset) compared to the generation numbers in the top layer (toplogical level). This violates the expectation that the generation of a parent is strictly smaller than the generation of a child. In such cases, we revert to using topological levels for all layers to maintain backwards compatability.
s/toplogical/topological
But also, we don't want to phrase this as "in this case, we do the wrong thing" but instead
A naive approach of using the newest available generation number from each layer would lead to violated expectations: the lower layer would use corrected commit dates which are much larger than the topological levels of the higher layer. For this reason, Git inspects each layer to see if any layer is missing corrected commit dates. In such a case, Git only uses topological levels.
Quoted 3 lines
When writing a new layer in split commit-graph, we write a GDAT chunk only if the topmost layer has a GDAT chunk. This guarantees that if a lyer has GDAT chunk, all lower layers must have a GDAT chunk as well.
s/lyer/layer
Perhaps leaving this at a higher level than referencing "GDAT chunk" is advisable. Perhaps use "we write corrected commit dates" or "all lower layers must store corrected commit dates as well", for example.
Quoted 4 lines
Rewriting layers follows similar approach: if the topmost layer below set of layers being rewriteen (in the split commit-graph chain) exists, and it does not contain GDAT chunk, then the result of rewrite does not have GDAT chunks either.
Quoted 7 lines
This could use more positive language to make it clear that sometimes we _do_ want to write corrected commit dates when merging layers:
When merging layers, we do not consider whether the merged layers had corrected commit dates. Instead, the new layer will have corrected commit dates if and only if all existing layers below the new layer have corrected commit dates.
Thanks, that is a great suggestion! Using positive language is more straightforward and easier to understand.
Quoted 3 lines
Thanks,
-Stolee
Quoted 2 lines · Abhishek Kumar via GitGitGadget
This patch series implements the corrected commit date offsets as generation number v2, along with other pre-requisites.
I'm not sure if this level of detail is required in the cover letter for the series, but generation number v2 is corrected commit date; corrected commit date offsets is how we store this value in the commit-graph file.
Quoted 7 lines
Git uses topological levels in the commit-graph file for commit-graph traversal operations like git log --graph. Unfortunately, using topological levels can result in a worse performance than without them when compared with committer date as a heuristics. For example, git merge-base v4.8 v4.9 on the Linux repository walks 635,579 commits using topological levels and walks 167,468 using committer date.
I would say "committer date heuristics" instead of just "committer date", to be more exact.
Is this data generated using https://github.com/derrickstolee/gen-test
scripts?
Quoted 6 lines
Thus, the need for generation number v2 was born. New generation number needed to provide good performance, increment updates, and backward compatibility. Due to an unfortunate problem, we also needed a way to distinguish between the old and new generation number without incrementing graph version.
It would be nice to have reference email (or other place with details) for "unfortunate problem".
Quoted 11 lines
Various candidates were examined (https://github.com/derrickstolee/gen-test,
https://github.com/abhishekkumar2718/git/pull/1). The proposed generation number v2, Corrected Commit Date with Mononotically Increasing Offsets performed much worse than committer date (506,577 vs. 167,468 commits walked for git merge-base v4.8 v4.9) and was dropped.Using Generation Data chunk (GDAT) relieves the requirement of backward compatibility as we would continue to store topological levels in Commit Data (CDAT) chunk. Thus, Corrected Commit Date was chosen as generation number v2.
This is a bit of simplification, but good enough for a cover letter.
To be more exact, from various candidates the Corrected Commit Date was chosen. Then it turned out that old Git crashes on changed commit-graph format version value, so if the generation number v2 was to replace v1 it needed to be backward-compatibile: hence the idea of Corrected Commit Date with Monotonically Increasing Offsets. But with GDAT chunk to store generation number v2 (and for the time being leaving generation number v1, i.e. Topological Levels, in CDAT), we are no longer constrained by the requirement of backward-compatibility to make old Git work with commit-graph file created by new Git. So we could go back to Corrected Commit Date, and as you wrote above the backward-compatibile variant performs worse.
Quoted 4 lines
The Corrected Commit Date is defined as:
For a commit C, let its corrected commit date be the maximum of the commit date of C and the corrected commit dates of its parents.
Actually it needs to be "corrected commit dates of its parents plus 1" to fulfill the reachability condition for a generation number for a commit:
A can reach B => gen(A) < gen(B)
Of course it can be computed in simpler way, because
max_P (gen(P) + 1) == max_P (gen(P)) + 1
Quoted 3 lines
Then corrected
commit date offset is the difference between corrected commit date of C and commit date of C.
All right.
Quoted 7 lines
We will introduce an additional commit-graph chunk, Generation Data chunk, and store corrected commit date offsets in GDAT chunk while storing topological levels in CDAT chunk. The old versions of Git would ignore GDAT chunk, using topological levels from CDAT chunk. In contrast, new versions of Git would use corrected commit dates, falling back to topological level if the generation data chunk is absent in the commit-graph file.
All right.
However I think the cover letter should also describe what should happen in a mixed version environment (for example new Git on command line, copy of old Git used by GUI client), and in particular what should happen in a mixed-chain case - both for reading and for writing the commit-graph file.
For *writing*: because old Git would create commit-graph layers without the GDAT chunk, to simplify the behavior and make easy to reason about commit-graph data (the situation should be not that common, and transient -- it should get more rare as the time goes), we want the following behavior from new Git:
- If top layer contains the GDAT chunk, or we are rewriting commit-graph file (--split=replace), or we are merging layers and there are no layers without GDAT chunk below set of layers that are merged, then
write commit-graph file or commit-graph layer with GDAT chunk,
otherwise
write commit-graph layer without GDAT chunk.
This means that there are commit-graph layers without GDAT chunk if and only if the top layer is also without GDAT chunk.
For *reading* we want to use generation number v2 (corrected commit date) if possible, and fall back to generation number v1 (topological levels).
- If the top layer contains the GDAT chunk (or maybe even if the topmost layer that involves all commits in question, not necessarily the top layer in the full commit-graph chain), then use generation number v2
- commit_graph_data->generation stores corrected commit date, computed as sum of committer date (from CDAT) and offset (from GDAT)
- A can reach B => gen(A) < gen(B)
- there is no need for committer date heuristics, and no need for limiting use of generation number to where there is a cutoff (to not hamper performance).
- If there are layers without GDAT chunks, which thanks to the write behavior means simply top layer without GDAT chunk, we need to turn off use of generation numbers or fall back to using topological levels
- commit_graph_data->generation stores topological levels, taken from CDAT chunk (30-bits)
- A can reach B => gen(A) < gen(B)
- we probably want to keep tie-breaking of sorting by generation number via committer date, and limit use of generation number as opposed to using committer date heuristics (with slop) to not make performance worse.
Quoted 11 lines
Thanks to Dr. Stolee, Dr. Narębski, and Taylor for their reviews on the first version.
I look forward to everyone's reviews!
Thanks
* Abhishek
Quoted 6 lines
----------------------------------------------------------------------------
Changes in version 3:
* Reordered patches as discussed in 1
[https://lore.kernel.org/git/[email protected]/]
If I remember it correctly this was done to always store in GDAT chunk corrected commit date offsets, isn't it?
Quoted 3 lines
* Split "implement corrected commit date" into two patches - one introducing the topo level slab and other implementing corrected commit dates.
All right.
I think it might be good idea to split off the change to tar file tests (as a preparatory patch), to make reviews and bisecting easier.
Quoted 1 line
* Extended split-commit-graph tests to verify at the end of test.
Do we also test for proper merging of split commit-graph layers, not only adding a new layer and a full rewrite (--split=replace)?
Quoted 2 lines
* Use topological levels as generation number if any of split commit-graph files do not have generation data chunk.
That is good for performance.
Quoted 4 lines
Changes in version 2:
* Add tests for generation data chunk.
Good.
Quoted 2 lines
* Add an option
GIT_TEST_COMMIT_GRAPH_NO_GDATto control whether to write generation data chunk.
Good, that is needed for testing mixed-version behavior.
Quoted 2 lines
* Compare commits with corrected commit dates if present in
paint_down_to_common().
All right, but see the caveat.
Quoted 1 line
* Update technical documentation.
Always a good thing.
Quoted 1 line
* Handle mixed graph version commit chains.
Where by "version" you mean generation number version - the commit-graph version number unfortunately needs to stay the same...
Quoted 1 line
* Improve commit messages for
^^^^^^
Something missing in this point, the sentence ends abruptly.
Quoted 1 line
* Revert unnecessary whitespace changes.
Thanks.
Quoted 1 line
* Split
uint_32->timestamp_tchange into a new commit.
It is usually better to keep the commits small. Good.
Good work!
Quoted 35 lines
Abhishek Kumar (11):
commit-graph: fix regression when computing bloom filter
revision: parse parent inindegree_walk_step()
commit-graph: consolidatefill_commit_graph_info
commit-graph: consolidatecompare_commits_by_gen
commit-graph: return 64-bit generation number
commit-graph: add a slab to store topological levels
commit-graph: implement corrected commit date
commit-graph: implement generation data chunk
commit-graph: use generation v2 only if entire chain does
commit-reach: use corrected commit dates inpaint_down_to_common()
doc: add corrected commit date info.../technical/commit-graph-format.txt | 12 +- Documentation/technical/commit-graph.txt | 45 ++-- commit-graph.c | 241 +++++++++++++----- commit-graph.h | 16 +- commit-reach.c | 49 ++-- commit-reach.h | 2 +- commit.c | 9 +- commit.h | 4 +- revision.c | 13 +- t/README | 3 + t/helper/test-read-graph.c | 2 + t/t4216-log-bloom.sh | 4 +- t/t5000-tar-tree.sh | 4 +- t/t5318-commit-graph.sh | 27 +- t/t5324-split-commit-graph.sh | 82 +++++- t/t6024-recursive-merge.sh | 4 +- t/t6600-test-reach.sh | 62 +++-- upload-pack.c | 2 +- 18 files changed, 396 insertions(+), 185 deletions(-)
Quoted 4 lines
base-commit:
7814e8a05a59c0cf5fb186661d1551c75d1299b5
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-676%2Fabhishekkumar2718%2Fcorrected_commit_date-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-676/abhishekkumar2718/corrected_commit_date-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/676
[...]
Best,
--
Jakub Narębski
A message that is not in the archive (sent off-list or not yet received): CANQwDwdKp7oKy9BeKdvKhwPUiq0R5MS8TCw-eWGCYCoMGv=G-g@mail.gmail.com
Quoted 26 lines · Jakub Narębski
Quoted 6 lines · Abhishek Kumar via GitGitGadget
We will introduce an additional commit-graph chunk, Generation Data chunk, and store corrected commit date offsets in GDAT chunk while storing topological levels in CDAT chunk. The old versions of Git would ignore GDAT chunk, using topological levels from CDAT chunk. In contrast, new versions of Git would use corrected commit dates, falling back to topological level if the generation data chunk is absent in the commit-graph file.
All right.
However I think the cover letter should also describe what should happen in a mixed version environment (for example new Git on command line, copy of old Git used by GUI client), and in particular what should happen in a mixed-chain case - both for reading and for writing the commit-graph file.
For *writing*: because old Git would create commit-graph layers without the GDAT chunk, to simplify the behavior and make easy to reason about commit-graph data (the situation should be not that common, and transient -- it should get more rare as the time goes), we want the following behavior from new Git:
- If top layer contains the GDAT chunk, or we are rewriting commit-graph file (--split=replace), or we are merging layers and there are no layers without GDAT chunk below set of layers that are merged, then
Quoted 8 lines
write commit-graph file or commit-graph layer with GDAT chunk,
otherwise
write commit-graph layer without GDAT chunk.
This means that there are commit-graph layers without GDAT chunk if and only if the top layer is also without GDAT chunk.
This seems very sane to me, and I'd be glad to see it spelled out in more specific detail. I was wondering this myself, and had to double check with Stolee off-list that my interpretation of Abhishek's code was correct.
But yes, only writing GDAT chunks when all layers in the chain have GDAT chunks makes sense, since we can't interoperate between corrected dates and topological levels. Since we can't fill in the GDAT data of layers generated in pre-GDAT versions of Git without invalidating the GDAT layers on-disk, there's no point to speculatively computing both chunks.
Merging rules are obviously correct, which is good. For what it's worth, the '--split=replace' case is what we'll really care about at GitHub, since it's unlikely we'd drop all existing commit-graph chains and rebuild them from scratch. More likely is that we'll let the new GDAT chunks trickle in over time when we run 'git commit-graph write' with '--split=replace', which happens "every so often".
Quoted 7 lines
For *reading* we want to use generation number v2 (corrected commit date) if possible, and fall back to generation number v1 (topological levels).
- If the top layer contains the GDAT chunk (or maybe even if the topmost layer that involves all commits in question, not necessarily the top layer in the full commit-graph chain), then use generation number v2
I don't follow this. If we have a multi-layer chain, either all or none of the layers have a GDAT chunk. So, "if the top layer contains the GDAT chunk" makes sense, since it implies that all layers have the GDAT chunk. I don't see how "even if the topmost layer that involves all commits in question" would be possible, since (if I'm understanding your description correctly), we can't have *some* of the layers having a GDAT chunk with others only having a CDAT chunk.
I'm a little confused here.
Quoted 5 lines
-
commit_graph_data->generationstores corrected commit date, computed as sum of committer date (from CDAT) and offset (from GDAT)- A can reach B => gen(A) < gen(B)
Quoted 7 lines
- there is no need for committer date heuristics, and no need for limiting use of generation number to where there is a cutoff (to not hamper performance).
- If there are layers without GDAT chunks, which thanks to the write behavior means simply top layer without GDAT chunk, we need to turn off use of generation numbers or fall back to using topological levels
Good, I'm glad that this can be a quick check (that we can cache for future reads, but I'm not even sure the caching would be necessary without measuring).
Quoted 10 lines
-
commit_graph_data->generationstores topological levels, taken from CDAT chunk (30-bits)- A can reach B => gen(A) < gen(B)
- we probably want to keep tie-breaking of sorting by generation number via committer date, and limit use of generation number as opposed to using committer date heuristics (with slop) to not make performance worse.
All makes very good sense, except for the one point I raised above.
Quoted 3 lines
Quoted 3 lines
Thanks to Dr. Stolee, Dr. Narębski, and Taylor for their reviews on the first version.
Thanks, Abhishek for your great work on this. I was feeling bad that I wasn't more involved in the early discussions about the transition plan, but what you, Stolee, and Jakub came up with all seems like what I would have suggested, anyway ;-).
Quoted 1 line
Jakub Narebski
Thanks,
Taylor
Quoted 48 lines · Taylor Blau
Quoted 27 lines · Jakub Narębski
Quoted 7 lines · Abhishek Kumar via GitGitGadget
We will introduce an additional commit-graph chunk, Generation Data chunk, and store corrected commit date offsets in GDAT chunk while storing topological levels in CDAT chunk. The old versions of Git would ignore GDAT chunk, using topological levels from CDAT chunk. In contrast, new versions of Git would use corrected commit dates, falling back to topological level if the generation data chunk is absent in the commit-graph file.
All right.
However I think the cover letter should also describe what should happen in a mixed version environment (for example new Git on command line, copy of old Git used by GUI client), and in particular what should happen in a mixed-chain case - both for reading and for writing the commit-graph file.
For *writing*: because old Git would create commit-graph layers without the GDAT chunk, to simplify the behavior and make easy to reason about commit-graph data (the situation should be not that common, and transient -- it should get more rare as the time goes), we want the following behavior from new Git:
- If top layer contains the GDAT chunk, or we are rewriting commit-graph file (--split=replace), or we are merging layers and there are no layers without GDAT chunk below set of layers that are merged, then
Quoted 8 lines
write commit-graph file or commit-graph layer with GDAT chunk,
otherwise
write commit-graph layer without GDAT chunk.
This means that there are commit-graph layers without GDAT chunk if and only if the top layer is also without GDAT chunk.
This seems very sane to me, and I'd be glad to see it spelled out in more specific detail. I was wondering this myself, and had to double check with Stolee off-list that my interpretation of Abhishek's code was correct.
But yes, only writing GDAT chunks when all layers in the chain have GDAT chunks makes sense, since we can't interoperate between corrected dates and topological levels. Since we can't fill in the GDAT data of layers generated in pre-GDAT versions of Git without invalidating the GDAT layers on-disk, there's no point to speculatively computing both chunks.
Quoted 6 lines
Merging rules are obviously correct, which is good. For what it's worth, the '--split=replace' case is what we'll really care about at GitHub, since it's unlikely we'd drop all existing commit-graph chains and rebuild them from scratch. More likely is that we'll let the new GDAT chunks trickle in over time when we run 'git commit-graph write' with '--split=replace', which happens "every so often".
To be more detailed, without '--split=replace' we would want the following layer merging behavior:
[layer with GDAT][with GDAT][without GDAT][without GDAT][without GDAT]
In the split commit-graph chain above, merging two topmost layers should create a layer without GDAT; merging three topmost layers (and any other layers, e.g. two middle ones) should create a layer
with GDAT.
Quoted 16 lines
Quoted 7 lines
For *reading* we want to use generation number v2 (corrected commit date) if possible, and fall back to generation number v1 (topological levels).
- If the top layer contains the GDAT chunk (or maybe even if the topmost layer that involves all commits in question, not necessarily the top layer in the full commit-graph chain), then use generation number v2
I don't follow this. If we have a multi-layer chain, either all or none of the layers have a GDAT chunk. So, "if the top layer contains the GDAT chunk" makes sense, since it implies that all layers have the GDAT chunk. I don't see how "even if the topmost layer that involves all commits in question" would be possible, since (if I'm understanding your description correctly), we can't have *some* of the layers having a GDAT chunk with others only having a CDAT chunk.
Quoted 1 line
I'm a little confused here.
This is only speculative, and most probably totally unnecessary complication (either that, or something that we would get for free). Assume that the command in question operates only on historical data; for example git log --topo-order HEAD~1000. If all commits (or, what's equivalent, most recent commits i.e. HEAD~1000) have their data in split commit-graph layers with GDAT, we can theoretically use generation number v2, even if there are some newer commits that have their data in layers without GDAT (and some even newer ones outside commit-graph files).
I hope that this explains my (possibly harebrained) idea.
Quoted 9 lines
Quoted 9 lines
-
commit_graph_data->generationstores corrected commit date, computed as sum of committer date (from CDAT) and offset (from GDAT)- A can reach B => gen(A) < gen(B)
- there is no need for committer date heuristics, and no need for limiting use of generation number to where there is a cutoff (to not hamper performance).
Quoted 7 lines
Quoted 3 lines
- If there are layers without GDAT chunks, which thanks to the write behavior means simply top layer without GDAT chunk, we need to turn off use of generation numbers or fall back to using topological levels
Good, I'm glad that this can be a quick check (that we can cache for future reads, but I'm not even sure the caching would be necessary without measuring).
There is a question where to store the information that we cannot use generation number v2 (that 'generation' contains topological levels and not corrected commit date):
- create new global variable
- store it in struct split_commit_graph_opts
- set chunk_generation_data to NULL for all graphs
in chain (it is in struct commit_graph)?
Quoted 14 lines
Quoted 10 lines
-
commit_graph_data->generationstores topological levels, taken from CDAT chunk (30-bits)- A can reach B => gen(A) < gen(B)
- we probably want to keep tie-breaking of sorting by generation number via committer date, and limit use of generation number as opposed to using committer date heuristics (with slop) to not make performance worse.
All makes very good sense, except for the one point I raised above.
Quoted 1 line
Quoted 1 line
Quoted 7 lines
Quoted 2 lines
Quoted 2 lines
Thanks to Dr. Stolee, Dr. Narębski, and Taylor for their reviews on the first version.
Thanks, Abhishek for your great work on this. I was feeling bad that I wasn't more involved in the early discussions about the transition plan, but what you, Stolee, and Jakub came up with all seems like what I would have suggested, anyway ;-).
Thank you for your work on improving this feature.
Best,
Signature · 1 line
Jakub Narebski
Quoted 17 lines · Jakub Narębski
Quoted 2 lines · Abhishek Kumar via GitGitGadget
This patch series implements the corrected commit date offsets as generation number v2, along with other pre-requisites.
I'm not sure if this level of detail is required in the cover letter for the series, but generation number v2 is corrected commit date; corrected commit date offsets is how we store this value in the commit-graph file.
Quoted 7 lines
Git uses topological levels in the commit-graph file for commit-graph traversal operations like git log --graph. Unfortunately, using topological levels can result in a worse performance than without them when compared with committer date as a heuristics. For example, git merge-base v4.8 v4.9 on the Linux repository walks 635,579 commits using topological levels and walks 167,468 using committer date.
Quoted 6 lines
I would say "committer date heuristics" instead of just "committer date", to be more exact.
Is this data generated using https://github.com/derrickstolee/gen-test
scripts?
Yes, it is.
Quoted 10 lines
Quoted 6 lines
Thus, the need for generation number v2 was born. New generation number needed to provide good performance, increment updates, and backward compatibility. Due to an unfortunate problem, we also needed a way to distinguish between the old and new generation number without incrementing graph version.
It would be nice to have reference email (or other place with details) for "unfortunate problem".
Will add.
Quoted 56 lines
Quoted 11 lines
Various candidates were examined (https://github.com/derrickstolee/gen-test,
https://github.com/abhishekkumar2718/git/pull/1). The proposed generation number v2, Corrected Commit Date with Mononotically Increasing Offsets performed much worse than committer date (506,577 vs. 167,468 commits walked for git merge-base v4.8 v4.9) and was dropped.Using Generation Data chunk (GDAT) relieves the requirement of backward compatibility as we would continue to store topological levels in Commit Data (CDAT) chunk. Thus, Corrected Commit Date was chosen as generation number v2.
This is a bit of simplification, but good enough for a cover letter.
To be more exact, from various candidates the Corrected Commit Date was chosen. Then it turned out that old Git crashes on changed commit-graph format version value, so if the generation number v2 was to replace v1 it needed to be backward-compatibile: hence the idea of Corrected Commit Date with Monotonically Increasing Offsets. But with GDAT chunk to store generation number v2 (and for the time being leaving generation number v1, i.e. Topological Levels, in CDAT), we are no longer constrained by the requirement of backward-compatibility to make old Git work with commit-graph file created by new Git. So we could go back to Corrected Commit Date, and as you wrote above the backward-compatibile variant performs worse.
Quoted 4 lines
The Corrected Commit Date is defined as:
For a commit C, let its corrected commit date be the maximum of the commit date of C and the corrected commit dates of its parents.
Actually it needs to be "corrected commit dates of its parents plus 1" to fulfill the reachability condition for a generation number for a commit:
A can reach B => gen(A) < gen(B)
Of course it can be computed in simpler way, because
max_P(gen(P) + 1) ==max_P(gen(P)) + 1Quoted 3 lines
Then corrected
commit date offset is the difference between corrected commit date of C and commit date of C.All right.
Quoted 7 lines
We will introduce an additional commit-graph chunk, Generation Data chunk, and store corrected commit date offsets in GDAT chunk while storing topological levels in CDAT chunk. The old versions of Git would ignore GDAT chunk, using topological levels from CDAT chunk. In contrast, new versions of Git would use corrected commit dates, falling back to topological level if the generation data chunk is absent in the commit-graph file.
Quoted 8 lines
All right.
However I think the cover letter should also describe what should happen in a mixed version environment (for example new Git on command line, copy of old Git used by GUI client), and in particular what should happen in a mixed-chain case - both for reading and for writing the commit-graph file.
Yes, definitely. Will add
Quoted 20 lines
For *writing*: because old Git would create commit-graph layers without the GDAT chunk, to simplify the behavior and make easy to reason about commit-graph data (the situation should be not that common, and transient -- it should get more rare as the time goes), we want the following behavior from new Git:
- If top layer contains the GDAT chunk, or we are rewriting commit-graph file (--split=replace), or we are merging layers and there are no layers without GDAT chunk below set of layers that are merged, then
write commit-graph file or commit-graph layer with GDAT chunk,
otherwise
write commit-graph layer without GDAT chunk.
This means that there are commit-graph layers without GDAT chunk if and only if the top layer is also without GDAT chunk.
Quoted 8 lines
For *reading* we want to use generation number v2 (corrected commit date) if possible, and fall back to generation number v1 (topological levels).
- If the top layer contains the GDAT chunk (or maybe even if the topmost layer that involves all commits in question, not necessarily the top layer in the full commit-graph chain), then use generation number v2
The current implementation checks the entire chain for GDAT, rather than just the topmost layer as we cannot assert that g would be the topmost layer of the chain.
See the discussion here: https://lore.kernel.org/git/20200814045957.GA1380@Abhishek-Arch/
It's one of drawbacks of having a single member 64-bit generation instead of two 32-bit members level and odate.
Quoted 38 lines
-
commit_graph_data->generationstores corrected commit date, computed as sum of committer date (from CDAT) and offset (from GDAT)- A can reach B => gen(A) < gen(B)
- there is no need for committer date heuristics, and no need for limiting use of generation number to where there is a cutoff (to not hamper performance).
- If there are layers without GDAT chunks, which thanks to the write behavior means simply top layer without GDAT chunk, we need to turn off use of generation numbers or fall back to using topological levels
-
commit_graph_data->generationstores topological levels, taken from CDAT chunk (30-bits)- A can reach B => gen(A) < gen(B)
- we probably want to keep tie-breaking of sorting by generation number via committer date, and limit use of generation number as opposed to using committer date heuristics (with slop) to not make performance worse.
Quoted 13 lines
Thanks to Dr. Stolee, Dr. Narębski, and Taylor for their reviews on the first version.
I look forward to everyone's reviews!
Thanks
* Abhishek
----------------------------------------------------------------------------
Quoted 8 lines
Quoted 4 lines
Changes in version 3:
* Reordered patches as discussed in 1
[https://lore.kernel.org/git/[email protected]/]If I remember it correctly this was done to always store in GDAT chunk corrected commit date offsets, isn't it?
Yes.
Quoted 6 lines
Quoted 3 lines
* Split "implement corrected commit date" into two patches - one introducing the topo level slab and other implementing corrected commit dates.
All right.
Quoted 8 lines
I think it might be good idea to split off the change to tar file tests (as a preparatory patch), to make reviews and bisecting easier.
Quoted 1 line
* Extended split-commit-graph tests to verify at the end of test.
Do we also test for proper merging of split commit-graph layers, not only adding a new layer and a full rewrite (--split=replace)?
We do not, will add a test at end. Thanks for pointing this out.
Quoted 22 lines
Quoted 2 lines
* Use topological levels as generation number if any of split commit-graph files do not have generation data chunk.
That is good for performance.
Quoted 4 lines
Changes in version 2:
* Add tests for generation data chunk.
Good.
Quoted 2 lines
* Add an option
GIT_TEST_COMMIT_GRAPH_NO_GDATto control whether to write generation data chunk.Good, that is needed for testing mixed-version behavior.
Quoted 2 lines
* Compare commits with corrected commit dates if present in
paint_down_to_common().All right, but see the caveat.
Quoted 9 lines
Quoted 1 line
* Update technical documentation.
Always a good thing.
Quoted 1 line
* Handle mixed graph version commit chains.
Where by "version" you mean generation number version - the commit-graph version number unfortunately needs to stay the same...
Yes, clarified.
Quoted 3 lines
Quoted 1 line
* Improve commit messages for
^^^^^^Something missing in this point, the sentence ends abruptly.
I didn't finish the sentence. Meant to say:
- Improve commit messages for "commit-graph: fix regression when computing bloom filter", "commit-graph: consolidate fill_commit_graph_info",
Quoted 15 lines
Quoted 2 lines
* Revert unnecessary whitespace changes.
Thanks.
Quoted 1 line
* Split
uint_32->timestamp_tchange into a new commit.It is usually better to keep the commits small. Good.
Good work!
...
--
Jakub Narębski
Hello,
Here is a summary of my comments and thoughts after carefully reviewing all patches in the series.
Quoted 1 line · Jakub Narębski
"Abhishek Kumar via GitGitGadget" <[email protected]> writes:
[...]
Quoted 9 lines
Quoted 4 lines
The Corrected Commit Date is defined as:
For a commit C, let its corrected commit date be the maximum of the commit date of C and the corrected commit dates of its parents.
Actually it needs to be "corrected commit dates of its parents plus 1" to fulfill the reachability condition for a generation number for a commit:
Quoted 5 lines
A can reach B => gen(A) < gen(B)
Of course it can be computed in simpler way, because
max_P(gen(P) + 1) ==max_P(gen(P)) + 1
I see that it is defined correctly in the documentation, which is more important than cover letter (which would not be stored in the repository for memory, even in the commit message for the merge).
[...]
Quoted 12 lines
However I think the cover letter should also describe what should happen in a mixed version environment (for example new Git on command line, copy of old Git used by GUI client), and in particular what should happen in a mixed-chain case - both for reading and for writing the commit-graph file.
For *writing*: because old Git would create commit-graph layers without the GDAT chunk, to simplify the behavior and make easy to reason about commit-graph data (the situation should be not that common, and transient -- it should get more rare as the time goes), we want the following behavior from new Git:
Quoted 5 lines
- If top layer contains the GDAT chunk, or we are rewriting commit-graph file (--split=replace), or we are merging layers and there are no layers without GDAT chunk below set of layers that are merged, then
write commit-graph file or commit-graph layer with GDAT chunk,
Actually we can simplify handling of merging layers, while still retaining the property that in mixed-version chain all GDAT-full layers are before / below GDAT-less layers.
Namely, if merging layers, and at least one layer being merged doesn't have GDAT chunk, then the result of merge wouldn't have either.
We can always switch to slightly more complicated behavior proposed above in quoted part later, perhaps as a followup commit.
Quoted 7 lines
otherwise
write commit-graph layer without GDAT chunk.
This means that there are commit-graph layers without GDAT chunk if and only if the top layer is also without GDAT chunk.
This might be not necessary if Git would always check the whole chain of split commit-graph layers for presence of GDAT-less layers.
But I still think it is a good idea to avoid having GDAT-less "holes".
Quoted 11 lines
For *reading* we want to use generation number v2 (corrected commit date) if possible, and fall back to generation number v1 (topological levels).
- If the top layer contains the GDAT chunk (or maybe even if the topmost layer that involves all commits in question, not necessarily the top layer in the full commit-graph chain), then use generation number v2
-
commit_graph_data->generationstores corrected commit date, computed as sum of committer date (from CDAT) and offset (from GDAT)
Or stored directly in GDAT, at the cost of increasing the file size by at most 7% (if I have done my math correctly).
See also the issue with clamping offsets (GENERATION_NUMBER_V2_OFFSET_MAX).
Quoted 14 lines
- A can reach B => gen(A) < gen(B)
- there is no need for committer date heuristics, and no need for limiting use of generation number to where there is a cutoff (to not hamper performance).
- If there are layers without GDAT chunks, which thanks to the write behavior means simply top layer without GDAT chunk, we need to turn off use of generation numbers or fall back to using topological levels
-
commit_graph_data->generationstores topological levels, taken from CDAT chunk (30-bits)
Quoted 6 lines
- A can reach B => gen(A) < gen(B)
- we probably want to keep tie-breaking of sorting by generation number via committer date, and limit use of generation number as opposed to using committer date heuristics (with slop) to not make performance worse.
And this is being done in this patch series. Good!
The thing I was worrying about turned out to be non-issue, as the comparison function in question is used only when writing, and in this case we have corrected commit date computer - though perhaps not being written (as far as I understand it, but I might be mistaken).
[...]
Quoted 3 lines
Quoted 3 lines
Abhishek Kumar (11):
commit-graph: fix regression when computing bloom filter
No problems, maybe just expanding a commit message and/or adding a comment.
Quoted 1 line
Quoted 1 line
revision: parse parent in
indegree_walk_step()
Looks good to me.
Quoted 1 line
Quoted 1 line
commit-graph: consolidate
fill_commit_graph_info
I think this commit could be split into three:
- fix to the 'generate tar with future mtime' test
as it is a hidden bug (when using commit-graph)
- simplify implementation of fill_commit_in_graph() by using fill_commit_graph_info()
- move loading date into fill_commit_graph_info() that uncovers the issue with 'generate tar with future mtime'
On the other hand because they are inter-related, those changes might be kept in a single commit.
In commit message greater care needs to be taken with fill_commit_graph_info() and fill_commit_in_graph(), when to use one and when to use the other. For example it is fill_commit_graph_info() that changes its behavior, and it is fill_commit_in_graph() that is getting simplified.
Quoted 1 line
Quoted 1 line
commit-graph: consolidate
compare_commits_by_gen
Looks good to me, though it might be good idea to add comments about the sorting order (inside comment) to appropriate header files.
Quoted 1 line
Quoted 1 line
commit-graph: return 64-bit generation number
This conversion misses one place, though it would be changed to use topological levels slab in next patch.
This patch also unnecessary introduces GENERATION_NUMBER_V1_INFINITY. There is no need for it: generation field can always simply use GENERATION_NUMBER_INFINITY for commits not in commit-graph.
Quoted 1 line
Quoted 1 line
commit-graph: add a slab to store topological levels
This is the patch that needs GENERATION_NUMBER_V1_INFINITY (or TOPOLOGICAL_LEVEL_INFINITY), not the previous patch.
Detailed analysis uncovered hidden bug in the code of compute_generation_numbers() that works only because of historical reasons (that topological levels in Git start from 1, not from 0). The problem is that the 'level' / 'generation' variable for commits not in graph, and therefore ones that needs to have its generation number computed, is 0 (default value on commit slab) and not GENERATION_NUMBER*_INFINITY as it should.
This issue is present since moving commit graph info data to commit-slab. We can simply document it and ignore (it works, if by accident), or try to fix it.
We need to handle GENERATION_NUMBER*_MAX clamping carefully in the future patches.
I think also that this patch needs a bit more detailed commit message.
Quoted 1 line
Quoted 1 line
commit-graph: implement corrected commit date
Looks good, though verify_commit_graph() now verifies *a* generation number used, be it v1 (topological level) or v2 (corrected commit date), so the variable rename is unnecessary. We verify that they fulfill the reachability condition promise, that is gen(parent) <= gen(commit), (the '=' is to include GENERATION_NUMBER*_MAX case), as it is what is used to speed up graph traversal.
We probably want to verify both topological level values in CDAT, and if they exist also corrected commit date values in GDAT. But that might be left for the future commit.
Quoted 1 line
Quoted 1 line
commit-graph: implement generation data chunk
To save up to 6-7% on commit-graph file size we store 32-bits corrected commit date offsets, instead of storing 64-bits corrected commit date.
However, as far as I understand it, using non-monotonic values for on-disk storage with limited field size, like 32-bits corrected commit date offset, leads to problems with GENERATION_NUMBER*_MAX. Namely, as I have written in detail in my reply to patch 11/11 in this series, there is no way to fulfill the reachability condition promise if we have to store offset which true value do not fit in 32-bits reserved for it.
This is extremly unlikely to happen in practice, but we need to be able to handle it somehow. We can store 64-bit corrected commit date, which has graph-monotonic values, and the problem goes away in theory and in practice (we would never have values that do not fit). We can keep storing 32-bit offsets, and simply do not use GDAT chunk if there is offset value that do not fit.
All this of course, provided that I am not wrong about this issue...
Quoted 1 line
Quoted 1 line
commit-graph: use generation v2 only if entire chain does
The commit message do not say anything about the *writing* side.
However, if we want to keep the requirement that GDAT-less layers in the split commit-graph chain must all be above any GDAT-containing layers, we need to consider how we want layer merging to behave. We could either opt for using GDAT whenever possible, or similify code and skip using GDAT if we are unsure.
The first approach would mean that if the topmost layer below set of layers being rewritten (in the split commit-graph chain) exists, and it does not contain GDAT chunk, then and only then the result of rewrite should not have GDAT chunk either.
The second approach is, I think, simpler: if any of layers that is being rewritten is GDAT-less (we need to check only the top layer, though), and we are not running full rewrite ('--split=replace'), then the result of rewrite should not have GDAT chunk either. We can switch to the first algorithm in later commit.
Whether we choose one or the other, we need test that doing layer merging do not break GDAT-inness requirement stated above.
Also, we can probably test that we are not using v1 and v2 at the same time with tests involving --is-ancestor, or --contains / --merged.
Quoted 1 line
Quoted 1 line
commit-reach: use corrected commit dates in
paint_down_to_common()
I think this commit could be split into two:
- disable commit graph for entire t6024-recursive-merge test
- use corrected commit dates in paint_down_to_common()
On the other hand because they are inter-related, those changes might be better kept in a single commit.
It would be nice to have some benchmark data, or at least stating that performance does not change (within the error bounds), using for example 'git merge-base v4.8 v4.9' in Linux kernel repository.
Quoted 1 line
Quoted 1 line
doc: add corrected commit date info
Looks good, there are a few places where 'generation number' (referring to the v1 version) should have been replaced with 'topological level'.
I am also unsure how the GDAT chunk can be "later modified to store future generation number related data.". I'd like to have an example, or for this statement to be removed (if it turns out to not be true, not without introducing yet another chunk type).
Quoted 1 line
Quoted 1 line
18 files changed, 396 insertions(+), 185 deletions(-)
Thanks for all your work.
Best regards,
--
Jakub Narębski
Hello,
Quoted 5 lines · Jakub Narębski
Hello,
Here is a summary of my comments and thoughts after carefully reviewing all patches in the series.
Thanks for the detailed review. It must have taken you a lot of time and focus to go through the patches, so I have really appreciate the effort.
I have been going over the comments and they are reasonable and very much needed. I will respond to the mails in the specific threads (to keep the discussion locally scoped and thus manageable), with doubts and comments that I have as I try to follow through the suggestions.
Quoted 3 lines
Best regards,
--
Jakub Narębski
Thanks
- Abhishek
v4 cover Implement Corrected Commit Date
In reply to Abhishek Kumar via GitGitGadgetThis patch series implements the corrected commit date offsets as generation number v2, along with other pre-requisites.
Git uses topological levels in the commit-graph file for commit-graph traversal operations like git log --graph. Unfortunately, using topological levels can result in a worse performance than without them when compared with committer date as a heuristics. For example, git merge-base v4.8 v4.9 on the Linux repository walks 635,579 commits using topological levels and walks 167,468 using committer date.
Thus, the need for generation number v2 was born. New generation number needed to provide good performance, increment updates, and backward compatibility. Due to an unfortunate problem 1
[https://public-inbox.org/git/[email protected]/], we also needed a way to distinguish between the old and new generation number without incrementing graph version.
Various candidates were examined (https://github.com/derrickstolee/gen-test,
https://github.com/abhishekkumar2718/git/pull/1). The proposed generation number v2, Corrected Commit Date with Mononotically Increasing Offsets performed much worse than committer date (506,577 vs. 167,468 commits walked for git merge-base v4.8 v4.9) and was dropped.
Using Generation Data chunk (GDAT) relieves the requirement of backward compatibility as we would continue to store topological levels in Commit Data (CDAT) chunk. Thus, Corrected Commit Date was chosen as generation number v2. The Corrected Commit Date is defined as:
For a commit C, let its corrected commit date be the maximum of the commit date of C and the corrected commit dates of its parents plus 1. Then corrected commit date offset is the difference between corrected commit date of C and commit date of C. As a special case, a root commit with timestamp zero has corrected commit date of 1 to be able distinguish it from GENERATION_NUMBER_ZERO (that is, an uncomputed corrected commit date).
We will introduce an additional commit-graph chunk, Generation Data chunk, and store corrected commit date offsets in GDAT chunk while storing topological levels in CDAT chunk. The old versions of Git would ignore GDAT chunk, using topological levels from CDAT chunk. In contrast, new versions of Git would use corrected commit dates, falling back to topological level if the generation data chunk is absent in the commit-graph file.
While storing corrected commit date offsets saves us 4 bytes per commit (as compared with storing corrected commit dates directly), it's possible for the offset to overflow the space allocated. To handle such cases, we introduce a new chunk, Generation Data Overflow (GDOV) that stores the corrected commit date. For overflowing offsets, we set MSB and store the position into the GDOV chunk, in a mechanism similar to the Extra Edges list chunk.
For mixed generation number environment (for example new Git on the command line, old Git used by GUI client), we can encounter a mixed-chain commit-graph (a commit-graph chain where some of split commit-graph files have GDAT chunk and others do not). As backward compatibility is one of the goals, we can define the following behavior:
While reading a mixed-chain commit-graph version, we fall back on topological levels as corrected commit dates and topological levels cannot be compared directly.
While writing on top of a split commit-graph, we check if the tip of the chain has a GDAT chunk. If it does, we append to the chain, writing GDAT chunk. Thus, we guarantee if the topmost split commit-graph file has a GDAT chunk, rest of the chain does too.
If the topmost split commit-graph file does not have a GDAT chunk (meaning it has been appended by the old Git), we write without GDAT chunk. We do write a GDAT chunk when the existing chain does not have GDAT chunk - when we are writing to the commit-graph chain with the 'replace' strategy.
Thanks to Dr. Stolee, Dr. Narębski, and Taylor for their reviews.
I look forward to everyone's reviews!
Thanks
* Abhishek
----------------------------------------------------------------------------
Changes in version 4:
* Added GDOV to handle overflows in generation data.
* Added a test for writing tip graph for a generation number v2 graph chain in t5324-split-commit-graph.sh
* Added a section on how mixed generation number chains are handled in Documentation/technical/commit-graph-format.txt
* Reverted unimportant whitespace, style changes in commit-graph.c
* Added header comments about the order of comparision for compare_commits_by_gen_then_commit_date in commit.h, compare_commits_by_gen in commit-graph.h
* Elaborated on why t6404 fails with corrected commit date and must be run with GIT_TEST_COMMIT_GRAPH=1in the commit "commit-reach: use corrected commit dates in paint_down_to_common()"
* Elaborated on write behavior for mixed generation number chains in the commit "commit-graph: use generation v2 only if entire chain does"
* Added notes about adding the topo_level slab to struct write_commit_graph_context as well as struct commit_graph.
* Clarified commit message for "commit-graph: consolidate fill_commit_graph_info"
* Removed the claim "GDAT can store future generation numbers" because it hasn't been tested yet.
Changes in version 3:
* Reordered patches as discussed in 2
[https://lore.kernel.org/git/[email protected]/]
.
* Split "implement corrected commit date" into two patches - one introducing the topo level slab and other implementing corrected commit dates.
* Extended split-commit-graph tests to verify at the end of test.
* Use topological levels as generation number if any of split commit-graph files do not have generation data chunk.
Changes in version 2:
* Add tests for generation data chunk.
* Add an option GIT_TEST_COMMIT_GRAPH_NO_GDAT to control whether to write generation data chunk.
* Compare commits with corrected commit dates if present in paint_down_to_common().
* Update technical documentation.
* Handle mixed generation commit chains.
* Improve commit messages for "commit-graph: fix regression when computing bloom filter", "commit-graph: consolidate fill_commit_graph_info",
* Revert unnecessary whitespace changes.
* Split uint_32 -> timestamp_t change into a new commit.
Abhishek Kumar (10):
commit-graph: fix regression when computing Bloom filters
revision: parse parent in indegree_walk_step()
commit-graph: consolidate fill_commit_graph_info
commit-graph: return 64-bit generation number
commit-graph: add a slab to store topological levels
commit-graph: implement corrected commit date
commit-graph: implement generation data chunk
commit-graph: use generation v2 only if entire chain does
commit-reach: use corrected commit dates in paint_down_to_common()
doc: add corrected commit date info
.../technical/commit-graph-format.txt | 21 +- Documentation/technical/commit-graph.txt | 62 ++++- commit-graph.c | 256 ++++++++++++++---- commit-graph.h | 17 +- commit-reach.c | 38 +-- commit-reach.h | 2 +- commit.c | 4 +- commit.h | 5 +- revision.c | 13 +- t/README | 3 + t/helper/test-read-graph.c | 4 + t/t4216-log-bloom.sh | 4 +- t/t5000-tar-tree.sh | 20 +- t/t5318-commit-graph.sh | 70 ++++- t/t5324-split-commit-graph.sh | 98 ++++++- t/t6404-recursive-merge.sh | 5 +- t/t6600-test-reach.sh | 68 ++--- upload-pack.c | 2 +- 18 files changed, 534 insertions(+), 158 deletions(-)
base-commit: d98273ba77e1ab9ec755576bc86c716a97bf59d7
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-676%2Fabhishekkumar2718%2Fcorrected_commit_date-v4
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-676/abhishekkumar2718/corrected_commit_date-v4
Pull-Request: https://github.com/gitgitgadget/git/pull/676
Range-diff vs v3:
1: c6b7ade7af ! 1: fae81b534b commit-graph: fix regression when computing bloom filter
@@ Metadata
Author: Abhishek Kumar <[email protected]>
## Commit message ##
- commit-graph: fix regression when computing bloom filter
+ commit-graph: fix regression when computing Bloom filters
commit_gen_cmp is used when writing a commit-graph to sort commits in generation order before computing Bloom filters. Since c49c82aa (commit:
@@ Commit message
'commit_graph_data_at(c)->generation') in order to access it while writing.
+ While measuring performance with `git commit-graph write --reachable
+ --changed-paths` on the linux repository led to around 1m40s for both
+ HEAD and master (and could be due to fault in my measurements), it is
+ still the "right" thing to do.
+
## commit-graph.c ##
2: e673867234 ! 2: 4470d91642 revision: parse parent in indegree_walk_step()
@@ revision.c: static void indegree_walk_step(struct rev_info *revs)
struct commit *parent = p->item;
int *pi = indegree_slab_at(&info->indegree, parent);
-+ if (parse_commit_gently(parent, 1) < 0)
++ if (repo_parse_commit_gently(revs->repo, parent, 1) < 0)
+ return;
+
if (*pi) 3: 18d5864f81 ! 3: 18bb3318a1 commit-graph: consolidate fill_commit_graph_info
@@ Commit message
implementation by calling fill_commit_graph_info() within fill_commit_in_graph().
- The test 'generate tar with future mtime' creates a commit with commit
- time of (2 ^ 36 + 1) seconds since EPOCH. The commit time overflows into
- generation number (within CDAT chunk) and has undefined behavior.
+ fill_commit_graph_info() used to not load committer data from commit data
+ chunk. However, with the corrected committer date, we have to load
+ committer date to calculate generation number value.
- The test used to pass as fill_commit_in_graph() guarantees the values of
- graph position and generation number, and did not load timestamp.
- However, with corrected commit date we will need load the timestamp as
- well to populate the generation number.
+ e51217e15 (t5000: test tar files that overflow ustar headers,
+ 30-06-2016) introduced a test 'generate tar with future mtime' that
+ creates a commit with committer date of (2 ^ 36 + 1) seconds since
+ EPOCH. The CDAT chunk provides 34-bits for storing committer date, thus
+ committer time overflows into generation number (within CDAT chunk) and
+ has undefined behavior.
- Let's fix the test by setting a timestamp of (2 ^ 34 - 1) seconds.
+ The test used to pass as fill_commit_graph_info() would not set struct
+ member date of struct commit and loads committer date from the object
+ database, generating a tar file with the expected mtime.
+
+ However, with corrected commit date, we will load the committer date
+ from CDAT chunk (truncated to lower 34-bits to populate the generation
+ number. Thus, Git sets date and generates tar file with the truncated
+ mtime.
+
+ The ustar format (the header format used by most modern tar programs)
+ only has room for 11 (or 12, depending om some implementations) octal
+ digits for the size and mtime of each files.
+
+ Thus, setting a timestamp of 2 ^ 33 + 1 would overflow the 11-octal
+ digit implementations while still fitting into commit data chunk.
+
+ Since we want to test 12-octal digit implementations of ustar as well,
+ let's modify the existing test to no longer use commit-graph file.
@@ commit-graph.c: static int fill_commit_in_graph(struct repository *r,
- graph_data->graph_pos = pos;
lex_index = pos - g->num_commits_in_base;
-
-- commit_data = g->chunk_commit_data + (g->hash_len + 16) * lex_index;
-+ commit_data = g->chunk_commit_data + GRAPH_DATA_WIDTH * lex_index;
+ commit_data = g->chunk_commit_data + (g->hash_len + 16) * lex_index;
item->object.parsed = 1;
@@ commit-graph.c: static int fill_commit_in_graph(struct repository *r,
edge_value = get_be32(commit_data + g->hash_len);
## t/t5000-tar-tree.sh ##
-@@ t/t5000-tar-tree.sh: test_expect_success TIME_IS_64BIT 'set up repository with far-future commit' ' +@@ t/t5000-tar-tree.sh: test_expect_success TAR_HUGE,LONG_IS_64BIT 'system tar can read our huge size' '
+ test_cmp expect actual
+ '
+
++test_expect_success TIME_IS_64BIT 'set up repository with far-future commit' '
++ rm -f .git/index &&
++ echo foo >file &&
++ git add file &&
++ GIT_COMMITTER_DATE="@17179869183 +0000" \
++ git commit -m "tempori parendum"
++'
++
++test_expect_success TIME_IS_64BIT 'generate tar with future mtime' '
++ git archive HEAD >future.tar
++'
++
++test_expect_success TAR_HUGE,TIME_IS_64BIT,TIME_T_IS_64BIT 'system tar can read our future mtime' '
++ echo 2514 >expect &&
++ tar_info future.tar | cut -d" " -f2 >actual &&
++ test_cmp expect actual
++'
++
+ test_expect_success TIME_IS_64BIT 'set up repository with far-future commit' '
rm -f .git/index &&
echo content >file &&
git add file &&
- GIT_COMMITTER_DATE="@68719476737 +0000" \
-+ GIT_COMMITTER_DATE="@17179869183 +0000" \
++ GIT_TEST_COMMIT_GRAPH=0 GIT_COMMITTER_DATE="@68719476737 +0000" \
git commit -m "tempori parendum"
'
-@@ t/t5000-tar-tree.sh: test_expect_success TIME_IS_64BIT 'generate tar with future mtime' '
- '
-
- test_expect_success TAR_HUGE,TIME_IS_64BIT,TIME_T_IS_64BIT 'system tar can read our future mtime' '
-- echo 4147 >expect &&
-+ echo 2514 >expect &&
- tar_info future.tar | cut -d" " -f2 >actual &&
- test_cmp expect actual
- '
4: 6a0cde983d < -: ---------- commit-graph: consolidate compare_commits_by_gen 5: 6be759a954 ! 4: 011b0aa497 commit-graph: return 64-bit generation number
@@ Commit message
commit_graph_generation(), use timestamp_t for local variables and define GENERATION_NUMBER_INFINITY as (2 ^ 63 - 1) instead.
+ We rename GENERATION_NUMBER_MAX to GENERATION_NUMBER_V1_MAX to
+ represent the largest topological level we can store in the commit data
+ chunk.
+
+ With corrected commit dates implemented, we will have two such *_MAX
+ variables to denote the largest offset and largest topological level
+ that can be stored.
+
## commit-graph.c ##
@@ commit-graph.c: uint32_t commit_graph_position(const struct commit *c)
{
struct commit_graph_data *data =
commit_graph_data_slab_peek(&commit_graph_data_slab, c);
-@@ commit-graph.c: uint32_t commit_graph_generation(const struct commit *c)
- int compare_commits_by_gen(const void *_a, const void *_b)
- {
- const struct commit *a = _a, *b = _b;
-- const uint32_t generation_a = commit_graph_generation(a);
-- const uint32_t generation_b = commit_graph_generation(b);
-+ const timestamp_t generation_a = commit_graph_generation(a);
-+ const timestamp_t generation_b = commit_graph_generation(b);
-
- /* older commits first */
- if (generation_a < generation_b)
@@ commit-graph.c: static int commit_gen_cmp(const void *va, const void *vb)
const struct commit *a = *(const struct commit **)va;
const struct commit *b = *(const struct commit **)vb;
@@ commit-graph.c: static int commit_gen_cmp(const void *va, const void *vb)
if (generation_a < generation_b)
return -1;
@@ commit-graph.c: static void compute_generation_numbers(struct write_commit_graph_context *ctx)
- uint32_t generation = commit_graph_data_at(ctx->commits.list[i])->generation;
+ _("Computing commit graph generation numbers"),
+ ctx->commits.nr);
+ for (i = 0; i < ctx->commits.nr; i++) {
+- uint32_t generation = commit_graph_data_at(ctx->commits.list[i])->generation;
++ timestamp_t generation = commit_graph_data_at(ctx->commits.list[i])->generation;
display_progress(ctx->progress, i + 1);
-- if (generation != GENERATION_NUMBER_INFINITY &&
-+ if (generation != GENERATION_NUMBER_V1_INFINITY &&
- generation != GENERATION_NUMBER_ZERO)
- continue;
-
+ if (generation != GENERATION_NUMBER_INFINITY &&
@@ commit-graph.c: static void compute_generation_numbers(struct write_commit_graph_context *ctx)
- for (parent = current->parents; parent; parent = parent->next) {
- generation = commit_graph_data_at(parent->item)->generation;
-
-- if (generation == GENERATION_NUMBER_INFINITY ||
-+ if (generation == GENERATION_NUMBER_V1_INFINITY ||
- generation == GENERATION_NUMBER_ZERO) {
- all_parents_computed = 0;
- commit_list_insert(parent->item, &list);
+ data->generation = max_generation + 1;
+ pop_commit(&list);
+
+- if (data->generation > GENERATION_NUMBER_MAX)
+- data->generation = GENERATION_NUMBER_MAX;
++ if (data->generation > GENERATION_NUMBER_V1_MAX)
++ data->generation = GENERATION_NUMBER_V1_MAX;
+ }
+ }
+ }
@@ commit-graph.c: int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags)
for (i = 0; i < g->num_commits; i++) {
struct commit *graph_commit, *odb_commit;
@@ commit-graph.c: int verify_commit_graph(struct repository *r, struct commit_grap
display_progress(progress, i + 1);
hashcpy(cur_oid.hash, g->chunk_oid_lookup + g->hash_len * i);
+@@ commit-graph.c: int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags)
+ continue;
+
+ /*
+- * If one of our parents has generation GENERATION_NUMBER_MAX, then
+- * our generation is also GENERATION_NUMBER_MAX. Decrement to avoid
++ * If one of our parents has generation GENERATION_NUMBER_V1_MAX, then
++ * our generation is also GENERATION_NUMBER_V1_MAX. Decrement to avoid
+ * extra logic in the following condition.
+ */
+- if (max_generation == GENERATION_NUMBER_MAX)
++ if (max_generation == GENERATION_NUMBER_V1_MAX)
+ max_generation--;
+
+ generation = commit_graph_generation(graph_commit);
## commit-graph.h ##
@@ commit-graph.h: void disable_commit_graph(struct repository *r);
@@ commit-graph.h: void disable_commit_graph(struct repository *r);
-uint32_t commit_graph_generation(const struct commit *); +timestamp_t commit_graph_generation(const struct commit *);
uint32_t commit_graph_position(const struct commit *);
-
- int compare_commits_by_gen(const void *_a, const void *_b);
+ #endif
## commit-reach.c ##
@@ commit-reach.c: static int queue_has_nonstale(struct prio_queue *queue)
@@ commit-reach.c: int repo_in_merge_bases_many(struct repository *r, struct commit
{
struct commit_list *bases;
int ret = 0, i;
-- uint32_t generation, min_generation = GENERATION_NUMBER_INFINITY;
-+ timestamp_t generation, min_generation = GENERATION_NUMBER_INFINITY;
+- uint32_t generation, max_generation = GENERATION_NUMBER_ZERO;
++ timestamp_t generation, max_generation = GENERATION_NUMBER_INFINITY;
if (repo_parse_commit(r, commit))
return ret;
@@ commit-reach.c: static enum contains_result contains_tag_algo(struct commit *can
struct commit *c = p->item; load_commit_graph_info(the_repository, c);
generation = commit_graph_generation(c);
+@@ commit-reach.c: static int compare_commits_by_gen(const void *_a, const void *_b)
+ const struct commit *a = *(const struct commit * const *)_a;
+ const struct commit *b = *(const struct commit * const *)_b;
+
+- uint32_t generation_a = commit_graph_generation(a);
+- uint32_t generation_b = commit_graph_generation(b);
++ timestamp_t generation_a = commit_graph_generation(a);
++ timestamp_t generation_b = commit_graph_generation(b);
+
+ if (generation_a < generation_b)
+ return -1;
@@ commit-reach.c: int can_all_from_reach_with_flag(struct object_array *from,
unsigned int with_flag,
unsigned int assign_flag,
@@ commit-reach.h: int can_all_from_reach_with_flag(struct object_array *from,
int commit_date_cutoff);
+ ## commit.c ##
+@@ commit.c: int compare_commits_by_author_date(const void *a_, const void *b_,
+ int compare_commits_by_gen_then_commit_date(const void *a_, const void *b_, void *unused)
+ {
+ const struct commit *a = a_, *b = b_;
+- const uint32_t generation_a = commit_graph_generation(a),
+- generation_b = commit_graph_generation(b);
++ const timestamp_t generation_a = commit_graph_generation(a),
++ generation_b = commit_graph_generation(b);
+
+ /* newer commits first */
+ if (generation_a < generation_b)
+
## commit.h ##
@@
#include "commit-slab.h"
#define COMMIT_NOT_FROM_GRAPH 0xFFFFFFFF
-#define GENERATION_NUMBER_INFINITY 0xFFFFFFFF
+-#define GENERATION_NUMBER_MAX 0x3FFFFFFF
+#define GENERATION_NUMBER_INFINITY ((1ULL << 63) - 1)
-+#define GENERATION_NUMBER_V1_INFINITY 0xFFFFFFFF
- #define GENERATION_NUMBER_MAX 0x3FFFFFFF ++#define GENERATION_NUMBER_V1_MAX 0x3FFFFFFF #define GENERATION_NUMBER_ZERO 0
+ struct commit_list {
## revision.c ##
@@ revision.c: define_commit_slab(indegree_slab, int);
@@ revision.c: static void init_topo_walk(struct rev_info *revs)
- uint32_t generation;
+ timestamp_t generation;
- if (parse_commit_gently(c, 1))
+ if (repo_parse_commit_gently(revs->repo, c, 1))
continue;
@@ revision.c: static void expand_topo_walk(struct rev_info *revs, struct commit *commit)
for (p = commit->parents; p; p = p->next) {
6: b347dbb01b ! 5: e067f653ad commit-graph: add a slab to store topological levels
@@ Metadata
## Commit message ##
commit-graph: add a slab to store topological levels
- As we are writing topological levels to commit data chunk to ensure
- backwards compatibility with "Old" Git and the member `generation` of
- struct commit_graph_data will store corrected commit date in a later
- commit, let's introduce a commit-slab to store topological levels while
- writing commit-graph.
+ In a later commit we will introduce corrected commit date as the
+ generation number v2. This value will be stored in the new seperate
+ Generation Data chunk. However, to ensure backwards compatibility with
+ "Old" Git we need to continue to write generation number v1, which is
+ topological level, to the commit data chunk. This means that we need to
+ compute both versions of generation numbers when writing the
+ commit-graph file. Therefore, let's introduce a commit-slab to store
+ topological levels; corrected commit date will be stored in the member
+ `generation` of struct commit_graph_data.
When Git creates a split commit-graph, it takes advantage of the
generation values that have been computed already and present in
existing commit-graph files.
- So, let's add a pointer to struct commit_graph to the topological level
- commit-slab and populate it with topological levels while writing a
- split commit-graph.
+ So, let's add a pointer to struct commit_graph as well as struct
+ write_commit_graph_context to the topological level commit-slab
+ and populate it with topological levels while writing a commit-graph
+ file.
Signed-off-by: Abhishek Kumar <[email protected]>
@@ commit-graph.c: struct write_commit_graph_context {
order_by_pack:1;
+ struct topo_level_slab *topo_levels;
- const struct split_commit_graph_opts *split_opts;
+ const struct commit_graph_opts *opts;
size_t total_bloom_filter_data_size;
const struct bloom_filter_settings *bloom_settings;
@@ commit-graph.c: static int write_graph_chunk_data(struct hashfile *f,
@@ commit-graph.c: static void compute_generation_numbers(struct write_commit_graph
_("Computing commit graph generation numbers"),
ctx->commits.nr);
for (i = 0; i < ctx->commits.nr; i++) {
-- uint32_t generation = commit_graph_data_at(ctx->commits.list[i])->generation;
-+ uint32_t level = *topo_level_slab_at(ctx->topo_levels, ctx->commits.list[i]);
+- timestamp_t generation = commit_graph_data_at(ctx->commits.list[i])->generation;
++ timestamp_t level = *topo_level_slab_at(ctx->topo_levels, ctx->commits.list[i]);
display_progress(ctx->progress, i + 1);
-- if (generation != GENERATION_NUMBER_V1_INFINITY &&
+- if (generation != GENERATION_NUMBER_INFINITY &&
- generation != GENERATION_NUMBER_ZERO)
-+ if (level != GENERATION_NUMBER_V1_INFINITY &&
++ if (level != GENERATION_NUMBER_INFINITY &&
+ level != GENERATION_NUMBER_ZERO)
continue;
@@ commit-graph.c: static void compute_generation_numbers(struct write_commit_graph
- generation = commit_graph_data_at(parent->item)->generation;
+ level = *topo_level_slab_at(ctx->topo_levels, parent->item);
-- if (generation == GENERATION_NUMBER_V1_INFINITY ||
+- if (generation == GENERATION_NUMBER_INFINITY ||
- generation == GENERATION_NUMBER_ZERO) {
-+ if (level == GENERATION_NUMBER_V1_INFINITY ||
++ if (level == GENERATION_NUMBER_INFINITY ||
+ level == GENERATION_NUMBER_ZERO) {
all_parents_computed = 0;
commit_list_insert(parent->item, &list);
@@ commit-graph.c: static void compute_generation_numbers(struct write_commit_graph
- data->generation = max_generation + 1;
pop_commit(&list);
-- if (data->generation > GENERATION_NUMBER_MAX)
-- data->generation = GENERATION_NUMBER_MAX;
-+ if (max_level > GENERATION_NUMBER_MAX - 1)
-+ max_level = GENERATION_NUMBER_MAX - 1;
+- if (data->generation > GENERATION_NUMBER_V1_MAX)
+- data->generation = GENERATION_NUMBER_V1_MAX;
++ if (max_level > GENERATION_NUMBER_V1_MAX - 1)
++ max_level = GENERATION_NUMBER_V1_MAX - 1;
+ *topo_level_slab_at(ctx->topo_levels, current) = max_level + 1;
}
}
}
@@ commit-graph.c: int write_commit_graph(struct object_directory *odb,
- uint32_t i, count_distinct = 0;
int res = 0;
int replace = 0;
+ struct bloom_filter_settings bloom_settings = DEFAULT_BLOOM_FILTER_SETTINGS;
+ struct topo_level_slab topo_levels;
if (!commit_graph_compatible(the_repository))
return 0;
@@ commit-graph.c: int write_commit_graph(struct object_directory *odb,
- }
- }
+ bloom_settings.max_changed_paths);
+ ctx->bloom_settings = &bloom_settings;
+ init_topo_level_slab(&topo_levels);
+ ctx->topo_levels = &topo_levels;
@@ commit-graph.c: int write_commit_graph(struct object_directory *odb,
+ }
+ }
+
- if (pack_indexes) {
- ctx->order_by_pack = 1;
- if ((res = fill_oids_from_packs(ctx, pack_indexes)))
+ if (flags & COMMIT_GRAPH_WRITE_BLOOM_FILTERS)
+ ctx->changed_paths = 1;
+ if (!(flags & COMMIT_GRAPH_NO_WRITE_BLOOM_FILTERS)) {
## commit-graph.h ##
@@ commit-graph.h: struct commit_graph {
@@ commit-graph.h: struct commit_graph {
struct bloom_filter_settings *bloom_filter_settings;
};
-
- ## commit.h ##
-@@
- #define GENERATION_NUMBER_V1_INFINITY 0xFFFFFFFF
- #define GENERATION_NUMBER_MAX 0x3FFFFFFF
- #define GENERATION_NUMBER_ZERO 0
-+#define GENERATION_NUMBER_V2_OFFSET_MAX 0xFFFFFFFF
-
- struct commit_list {
- struct commit *item; 7: 4074ace65b ! 6: 694ef1ec08 commit-graph: implement corrected commit date
@@ Commit message
the maximum of its commit date and one more than the largest corrected commit date among its parents.
+ As a special case, a root commit with timestamp of zero (01.01.1970
+ 00:00:00Z) has corrected commit date of one, to be able to distinguish
+ from GENERATION_NUMBER_ZERO (that is, an uncomputed corrected commit
+ date).
+
To minimize the space required to store corrected commit date, Git stores corrected commit date offsets into the commit-graph file. The corrected commit date offset for a commit is defined as the difference between its corrected commit date and actual commit date.
+ Storing corrected commit date requires sizeof(timestamp_t) bytes, which
+ in most cases is 64 bits (uintmax_t). However, corrected commit date
+ offsets can be safely stored using only 32-bits. This halves the size
+ of GDAT chunk, which is a reduction of around 6% in the size of
+ commit-graph file.
+
+ However, using offsets be problematic if one of commits is malformed but
+ valid and has committerdate of 0 Unix time, as the offset would be the
+ same as corrected commit date and thus require 64-bits to be stored
+ properly.
+
While Git does not write out offsets at this stage, Git stores the
corrected commit dates in member generation of struct commit_graph_data.
It will begin writing commit date offsets with the introduction of
@@ commit-graph.c: static int commit_gen_cmp(const void *va, const void *vb)
@@ commit-graph.c: static void compute_generation_numbers(struct write_commit_graph_context *ctx)
ctx->commits.nr);
for (i = 0; i < ctx->commits.nr; i++) {
- uint32_t level = *topo_level_slab_at(ctx->topo_levels, ctx->commits.list[i]);
+ timestamp_t level = *topo_level_slab_at(ctx->topo_levels, ctx->commits.list[i]);
+ timestamp_t corrected_commit_date = commit_graph_data_at(ctx->commits.list[i])->generation;
display_progress(ctx->progress, i + 1);
- if (level != GENERATION_NUMBER_V1_INFINITY &&
+ if (level != GENERATION_NUMBER_INFINITY &&
- level != GENERATION_NUMBER_ZERO)
+ level != GENERATION_NUMBER_ZERO &&
+ corrected_commit_date != GENERATION_NUMBER_INFINITY &&
@@ commit-graph.c: static void compute_generation_numbers(struct write_commit_graph
for (parent = current->parents; parent; parent = parent->next) {
level = *topo_level_slab_at(ctx->topo_levels, parent->item);
+-
+ corrected_commit_date = commit_graph_data_at(parent->item)->generation;
-
- if (level == GENERATION_NUMBER_V1_INFINITY ||
+ if (level == GENERATION_NUMBER_INFINITY ||
- level == GENERATION_NUMBER_ZERO) {
+ level == GENERATION_NUMBER_ZERO ||
+ corrected_commit_date == GENERATION_NUMBER_INFINITY ||
@@ commit-graph.c: static void compute_generation_numbers(struct write_commit_graph
}
@@ commit-graph.c: static void compute_generation_numbers(struct write_commit_graph_context *ctx)
- if (max_level > GENERATION_NUMBER_MAX - 1)
- max_level = GENERATION_NUMBER_MAX - 1;
+ if (max_level > GENERATION_NUMBER_V1_MAX - 1)
+ max_level = GENERATION_NUMBER_V1_MAX - 1;
*topo_level_slab_at(ctx->topo_levels, current) = max_level + 1;
+
-+ if (current->date > max_corrected_commit_date)
++ if (current->date && current->date > max_corrected_commit_date)
+ max_corrected_commit_date = current->date - 1;
+ commit_graph_data_at(current)->generation = max_corrected_commit_date + 1;
}
}
}
-@@ commit-graph.c: int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags)
- for (i = 0; i < g->num_commits; i++) {
- struct commit *graph_commit, *odb_commit;
- struct commit_list *graph_parents, *odb_parents;
-- timestamp_t max_generation = 0;
-- timestamp_t generation;
-+ timestamp_t max_corrected_commit_date = 0;
-+ timestamp_t corrected_commit_date;
-
- display_progress(progress, i + 1);
- hashcpy(cur_oid.hash, g->chunk_oid_lookup + g->hash_len * i);
-@@ commit-graph.c: int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags)
- oid_to_hex(&graph_parents->item->object.oid),
- oid_to_hex(&odb_parents->item->object.oid));
-
-- generation = commit_graph_generation(graph_parents->item);
-- if (generation > max_generation)
-- max_generation = generation;
-+ corrected_commit_date = commit_graph_generation(graph_parents->item);
-+ if (corrected_commit_date > max_corrected_commit_date)
-+ max_corrected_commit_date = corrected_commit_date;
-
- graph_parents = graph_parents->next;
- odb_parents = odb_parents->next;
@@ commit-graph.c: int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags)
if (generation_zero == GENERATION_ZERO_EXISTS)
continue;
- /*
-- * If one of our parents has generation GENERATION_NUMBER_MAX, then
-- * our generation is also GENERATION_NUMBER_MAX. Decrement to avoid
+- * If one of our parents has generation GENERATION_NUMBER_V1_MAX, then
+- * our generation is also GENERATION_NUMBER_V1_MAX. Decrement to avoid
- * extra logic in the following condition.
- */
-- if (max_generation == GENERATION_NUMBER_MAX)
+- if (max_generation == GENERATION_NUMBER_V1_MAX)
- max_generation--;
-
-- generation = commit_graph_generation(graph_commit);
+ generation = commit_graph_generation(graph_commit);
- if (generation != max_generation + 1)
- graph_report(_("commit-graph generation for commit %s is %u != %u"),
-+ corrected_commit_date = commit_graph_generation(graph_commit);
-+ if (corrected_commit_date < max_corrected_commit_date + 1)
++ if (generation < max_generation + 1)
+ graph_report(_("commit-graph generation for commit %s is %"PRItime" < %"PRItime),
oid_to_hex(&cur_oid),
-- generation,
-- max_generation + 1);
-+ corrected_commit_date,
-+ max_corrected_commit_date + 1);
-
- if (graph_commit->date != odb_commit->date)
- graph_report(_("commit date for commit %s in commit-graph is %"PRItime" != %"PRItime),
+ generation,
+ max_generation + 1);
8: 4e746628ac ! 7: b903efe2ea commit-graph: implement generation data chunk
@@ Commit message
between graph versions in a backwards compatible manner.
We are going to introduce a new chunk called Generation Data chunk (or
- GDAT). GDAT stores generation number v2 (and any subsequent versions),
- whereas CDAT will still store topological level.
+ GDAT). GDAT stores corrected committer date offsets whereas CDAT will
+ still store topological level.
Old Git does not understand GDAT chunk and would ignore it, reading
topological levels from CDAT. New Git can parse GDAT and take advantage
@@ Commit message
which forces commit-graph file to be written without generation data
chunk to emulate a commit-graph file written by old Git.
+ While storing corrected commit date offset instead of the corrected
+ commit date saves us 4 bytes per commit, it's possible for the offsets
+ to overflow the 4-bytes allocated. As such overflows are exceedingly
+ rare, we use the following overflow management scheme:
+
+ We introduce a new commit-graph chunk, GENERATION_DATA_OVERFLOW ('GDOV')
+ to store corrected commit dates for commits with offsets greater than
+ GENERATION_NUMBER_V2_OFFSET_MAX.
+
+ If the offset is greater than GENERATION_NUMBER_V2_OFFSET_MAX, we set
+ the MSB of the offset and the other bits store the position of corrected
+ commit date in GDOV chunk, similar to how Extra Edge List is maintained.
+
+ We test the overflow-related code with the following repo history:
+
+ F - N - U
+ / \
+ U - N - U N
+ \ /
+ N - F - N
+
+ Where the commits denoted by U have committer date of zero seconds
+ since Unix epoch, the commits denoted by N have committer date of
+ 1112354055 (default committer date for the test suite) seconds since
+ Unix epoch and the commits denoted by F have committer date of
+ (2 ^ 31 - 2) seconds since Unix epoch.
+
+ The largest offset observed is 2 ^ 31, just large enough to overflow.
+
[1]: https://lore.kernel.org/git/[email protected]/
Signed-off-by: Abhishek Kumar <[email protected]>
@@ commit-graph.c: void git_test_write_commit_graph_or_die(void)
#define GRAPH_CHUNKID_OIDLOOKUP 0x4f49444c /* "OIDL" */
#define GRAPH_CHUNKID_DATA 0x43444154 /* "CDAT" */
+#define GRAPH_CHUNKID_GENERATION_DATA 0x47444154 /* "GDAT" */
++#define GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW 0x47444f56 /* "GDOV" */
#define GRAPH_CHUNKID_EXTRAEDGES 0x45444745 /* "EDGE" */
#define GRAPH_CHUNKID_BLOOMINDEXES 0x42494458 /* "BIDX" */
#define GRAPH_CHUNKID_BLOOMDATA 0x42444154 /* "BDAT" */
#define GRAPH_CHUNKID_BASE 0x42415345 /* "BASE" */
-#define MAX_NUM_CHUNKS 7
-+#define MAX_NUM_CHUNKS 8
++#define MAX_NUM_CHUNKS 9
#define GRAPH_DATA_WIDTH (the_hash_algo->rawsz + 16)
-@@ commit-graph.c: struct commit_graph *parse_commit_graph(void *graph_map, size_t graph_size)
+@@ commit-graph.c: void git_test_write_commit_graph_or_die(void)
+ #define GRAPH_MIN_SIZE (GRAPH_HEADER_SIZE + 4 * GRAPH_CHUNKLOOKUP_WIDTH \
+ + GRAPH_FANOUT_SIZE + the_hash_algo->rawsz)
+
++#define CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW (1ULL << 31)
++
+ /* Remember to update object flag allocation in object.h */
+ #define REACHABLE (1u<<15)
+
+@@ commit-graph.c: struct commit_graph *parse_commit_graph(struct repository *r,
graph->chunk_commit_data = data + chunk_offset;
break;
@@ commit-graph.c: struct commit_graph *parse_commit_graph(void *graph_map, size_t
+ else
+ graph->chunk_generation_data = data + chunk_offset;
+ break;
++
++ case GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW:
++ if (graph->chunk_generation_data_overflow)
++ chunk_repeated = 1;
++ else
++ graph->chunk_generation_data_overflow = data + chunk_offset;
++ break;
+
case GRAPH_CHUNKID_EXTRAEDGES:
if (graph->chunk_extra_edges)
chunk_repeated = 1;
+@@ commit-graph.c: static void fill_commit_graph_info(struct commit *item, struct commit_graph *g,
+ {
+ const unsigned char *commit_data;
+ struct commit_graph_data *graph_data;
+- uint32_t lex_index;
+- uint64_t date_high, date_low;
++ uint32_t lex_index, offset_pos;
++ uint64_t date_high, date_low, offset;
+
+ while (pos < g->num_commits_in_base)
+ g = g->base_graph;
@@ commit-graph.c: static void fill_commit_graph_info(struct commit *item, struct commit_graph *g,
date_low = get_be32(commit_data + g->hash_len + 12);
item->date = (timestamp_t)((date_high << 32) | date_low);
- graph_data->generation = get_be32(commit_data + g->hash_len + 8) >> 2;
-+ if (g->chunk_generation_data)
-+ graph_data->generation = item->date +
-+ (timestamp_t) get_be32(g->chunk_generation_data + sizeof(uint32_t) * lex_index);
-+ else
++ if (g->chunk_generation_data) {
++ offset = (timestamp_t) get_be32(g->chunk_generation_data + sizeof(uint32_t) * lex_index);
++
++ if (offset & CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW) {
++ offset_pos = offset ^ CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW;
++ graph_data->generation = get_be64(g->chunk_generation_data_overflow + 8 * offset_pos);
++ } else
++ graph_data->generation = item->date + offset;
++ } else
+ graph_data->generation = get_be32(commit_data + g->hash_len + 8) >> 2;
if (g->topo_levels)
*topo_level_slab_at(g->topo_levels, item) = get_be32(commit_data + g->hash_len + 8) >> 2;
+@@ commit-graph.c: struct write_commit_graph_context {
+ struct packed_oid_list oids;
+ struct packed_commit_list commits;
+ int num_extra_edges;
++ int num_generation_data_overflows;
+ unsigned long approx_nr_objects;
+ struct progress *progress;
+ int progress_done;
@@ commit-graph.c: struct write_commit_graph_context {
report_progress:1,
split:1,
@@ commit-graph.c: struct write_commit_graph_context {
+ write_generation_data:1;
struct topo_level_slab *topo_levels;
- const struct split_commit_graph_opts *split_opts;
+ const struct commit_graph_opts *opts;
@@ commit-graph.c: static int write_graph_chunk_data(struct hashfile *f,
return 0;
}
@@ commit-graph.c: static int write_graph_chunk_data(struct hashfile *f,
+static int write_graph_chunk_generation_data(struct hashfile *f,
+ struct write_commit_graph_context *ctx)
+{
-+ int i;
++ int i, num_generation_data_overflows = 0;
+ for (i = 0; i < ctx->commits.nr; i++) {
+ struct commit *c = ctx->commits.list[i];
+ timestamp_t offset = commit_graph_data_at(c)->generation - c->date;
+ display_progress(ctx->progress, ++ctx->progress_cnt);
+
-+ if (offset > GENERATION_NUMBER_V2_OFFSET_MAX)
-+ offset = GENERATION_NUMBER_V2_OFFSET_MAX;
++ if (offset > GENERATION_NUMBER_V2_OFFSET_MAX) {
++ offset = CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW | num_generation_data_overflows;
++ num_generation_data_overflows++;
++ }
++
+ hashwrite_be32(f, offset);
+ }
+
+ return 0;
+}
++
++static int write_graph_chunk_generation_data_overflow(struct hashfile *f,
++ struct write_commit_graph_context *ctx)
++{
++ int i;
++ for (i = 0; i < ctx->commits.nr; i++) {
++ struct commit *c = ctx->commits.list[i];
++ timestamp_t offset = commit_graph_data_at(c)->generation - c->date;
++ display_progress(ctx->progress, ++ctx->progress_cnt);
++
++ if (offset > GENERATION_NUMBER_V2_OFFSET_MAX) {
++ hashwrite_be32(f, offset >> 32);
++ hashwrite_be32(f, (uint32_t) offset);
++ }
++ }
++
++ return 0;
++}
+
static int write_graph_chunk_extra_edges(struct hashfile *f,
-- struct write_commit_graph_context *ctx)
-+ struct write_commit_graph_context *ctx)
+ struct write_commit_graph_context *ctx)
{
- struct commit **list = ctx->commits.list;
- struct commit **last = ctx->commits.list + ctx->commits.nr;
+@@ commit-graph.c: static void compute_generation_numbers(struct write_commit_graph_context *ctx)
+
+ if (current->date && current->date > max_corrected_commit_date)
+ max_corrected_commit_date = current->date - 1;
++
+ commit_graph_data_at(current)->generation = max_corrected_commit_date + 1;
++
++ if (commit_graph_data_at(current)->generation - current->date > GENERATION_NUMBER_V2_OFFSET_MAX)
++ ctx->num_generation_data_overflows++;
+ }
+ }
+ }
@@ commit-graph.c: static int write_commit_graph_file(struct write_commit_graph_context *ctx)
chunks[2].id = GRAPH_CHUNKID_DATA;
chunks[2].size = (hashsz + 16) * ctx->commits.nr;
@@ commit-graph.c: static int write_commit_graph_file(struct write_commit_graph_con
+ chunks[num_chunks].size = sizeof(uint32_t) * ctx->commits.nr;
+ chunks[num_chunks].write_fn = write_graph_chunk_generation_data;
+ num_chunks++;
++ }
++ if (ctx->num_generation_data_overflows) {
++ chunks[num_chunks].id = GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW;
++ chunks[num_chunks].size = sizeof(timestamp_t) * ctx->num_generation_data_overflows;
++ chunks[num_chunks].write_fn = write_graph_chunk_generation_data_overflow;
++ num_chunks++;
+ }
if (ctx->num_extra_edges) {
chunks[num_chunks].id = GRAPH_CHUNKID_EXTRAEDGES;
chunks[num_chunks].size = 4 * ctx->num_extra_edges;
@@ commit-graph.c: int write_commit_graph(struct object_directory *odb,
ctx->split = flags & COMMIT_GRAPH_WRITE_SPLIT ? 1 : 0;
- ctx->split_opts = split_opts;
+ ctx->opts = opts;
ctx->total_bloom_filter_data_size = 0;
+ ctx->write_generation_data = 1;
++ ctx->num_generation_data_overflows = 0;
- if (flags & COMMIT_GRAPH_WRITE_BLOOM_FILTERS)
- ctx->changed_paths = 1;
+ bloom_settings.bits_per_entry = git_env_ulong("GIT_TEST_BLOOM_SETTINGS_BITS_PER_ENTRY",
+ bloom_settings.bits_per_entry);
## commit-graph.h ##
@@
@@ commit-graph.h: struct commit_graph {
const unsigned char *chunk_oid_lookup;
const unsigned char *chunk_commit_data;
+ const unsigned char *chunk_generation_data;
++ const unsigned char *chunk_generation_data_overflow;
const unsigned char *chunk_extra_edges;
const unsigned char *chunk_base_graphs;
const unsigned char *chunk_bloom_indexes;
+ ## commit.h ##
+@@
+ #define GENERATION_NUMBER_INFINITY ((1ULL << 63) - 1)
+ #define GENERATION_NUMBER_V1_MAX 0x3FFFFFFF
+ #define GENERATION_NUMBER_ZERO 0
++#define GENERATION_NUMBER_V2_OFFSET_MAX ((1ULL << 31) - 1)
+
+ struct commit_list {
+ struct commit *item;
+
## t/README ##
@@ t/README: GIT_TEST_COMMIT_GRAPH=<boolean>, when true, forces the commit-graph to
be written after every 'git commit' command, and overrides the
@@ t/helper/test-read-graph.c: int cmd__read_graph(int argc, const char **argv)
printf(" commit_metadata");
+ if (graph->chunk_generation_data)
+ printf(" generation_data");
++ if (graph->chunk_generation_data_overflow)
++ printf(" generation_data_overflow");
if (graph->chunk_extra_edges)
printf(" extra_edges");
if (graph->chunk_bloom_indexes)
## t/t4216-log-bloom.sh ##
@@ t/t4216-log-bloom.sh: test_expect_success 'setup test - repo, commits, commit graph, log outputs' '
- git commit-graph write --reachable --changed-paths
'
+
graph_read_expect () {
- NUM_CHUNKS=5
+ NUM_CHUNKS=6
cat >expect <<- EOF
- header: 43475048 1 1 $NUM_CHUNKS 0
+ header: 43475048 1 $(test_oid oid_version) $NUM_CHUNKS 0
num_commits: $1
- chunks: oid_fanout oid_lookup commit_metadata bloom_indexes bloom_data
+ chunks: oid_fanout oid_lookup commit_metadata generation_data bloom_indexes bloom_data
@@ t/t5318-commit-graph.sh: test_expect_success 'write graph in bare repo' '
'
graph_git_behavior 'bare repo with graph, commit 8 vs merge 1' bare commits/8 merge/1
-@@ t/t5318-commit-graph.sh: test_expect_success 'replace-objects invalidates commit-graph' '
+@@ t/t5318-commit-graph.sh: test_expect_success 'warn on improper hash version' '
test_expect_success 'git commit-graph verify' '
cd "$TRASH_DIRECTORY/full" &&
@@ t/t5318-commit-graph.sh: test_expect_success 'replace-objects invalidates commit
'
NUM_COMMITS=9
+@@ t/t5318-commit-graph.sh: test_expect_success 'corrupt commit-graph write (missing tree)' '
+ )
+ '
+
++test_commit_with_date() {
++ file="$1.t" &&
++ echo "$1" >"$file" &&
++ git add "$file" &&
++ GIT_COMMITTER_DATE="$2" GIT_AUTHOR_DATE="$2" git commit -m "$1"
++ git tag "$1"
++}
++
++test_expect_success 'overflow corrected commit date offset' '
++ objdir=".git/objects" &&
++ UNIX_EPOCH_ZERO="1970-01-01 00:00 +0000" &&
++ FUTURE_DATE="@2147483646 +0000" &&
++ test_oid_cache <<-EOF &&
++ oid_version sha1:1
++ oid_version sha256:2
++ EOF
++ cd "$TRASH_DIRECTORY" &&
++ mkdir repo &&
++ cd repo &&
++ git init &&
++ test_commit_with_date 1 "$UNIX_EPOCH_ZERO" &&
++ test_commit 2 &&
++ test_commit_with_date 3 "$UNIX_EPOCH_ZERO" &&
++ git commit-graph write --reachable &&
++ graph_read_expect 3 generation_data &&
++ test_commit_with_date 4 "$FUTURE_DATE" &&
++ test_commit 5 &&
++ test_commit_with_date 6 "$UNIX_EPOCH_ZERO" &&
++ git branch left &&
++ git reset --hard 3 &&
++ test_commit 7 &&
++ test_commit_with_date 8 "$FUTURE_DATE" &&
++ test_commit 9 &&
++ git branch right &&
++ git reset --hard 3 &&
++ git merge left right &&
++ git commit-graph write --reachable &&
++ graph_read_expect 10 "generation_data generation_data_overflow" &&
++ git commit-graph verify
++'
++
++graph_git_behavior 'overflow corrected commit date offset' repo left right
++
+ test_done
## t/t5324-split-commit-graph.sh ##
@@ t/t5324-split-commit-graph.sh: test_expect_success 'setup repo' '
@@ t/t5324-split-commit-graph.sh: test_expect_success 'setup repo' '
- base sha256:1496
+ base sha1:1408
+ base sha256:1528
- EOM
- '
+ oid_version sha1:1
+ oid_version sha256:2
@@ t/t5324-split-commit-graph.sh: graph_read_expect() {
NUM_BASE=$2
fi
cat >expect <<- EOF
-- header: 43475048 1 1 3 $NUM_BASE
-+ header: 43475048 1 1 4 $NUM_BASE
+- header: 43475048 1 $(test_oid oid_version) 3 $NUM_BASE
++ header: 43475048 1 $(test_oid oid_version) 4 $NUM_BASE
num_commits: $1
- chunks: oid_fanout oid_lookup commit_metadata
+ chunks: oid_fanout oid_lookup commit_metadata generation_data
@@ t/t6600-test-reach.sh: test_expect_success 'in_merge_bases:miss' '
+ test_all_modes in_merge_bases
'
+ test_expect_success 'in_merge_bases_many:hit' '
+@@ t/t6600-test-reach.sh: test_expect_success 'in_merge_bases_many:hit' '
+ X:commit-5-7
+ EOF
+ echo "in_merge_bases_many(A,X):1" >expect &&
+- test_three_modes in_merge_bases_many
++ test_all_modes in_merge_bases_many
+ '
+
+ test_expect_success 'in_merge_bases_many:miss' '
+@@ t/t6600-test-reach.sh: test_expect_success 'in_merge_bases_many:miss' '
+ X:commit-8-6
+ EOF
+ echo "in_merge_bases_many(A,X):0" >expect &&
+- test_three_modes in_merge_bases_many
++ test_all_modes in_merge_bases_many
+ '
+
+ test_expect_success 'in_merge_bases_many:miss-heuristic' '
+@@ t/t6600-test-reach.sh: test_expect_success 'in_merge_bases_many:miss-heuristic' '
+ X:commit-6-6
+ EOF
+ echo "in_merge_bases_many(A,X):0" >expect &&
+- test_three_modes in_merge_bases_many
++ test_all_modes in_merge_bases_many
+ '
+
test_expect_success 'is_descendant_of:hit' '
@@ t/t6600-test-reach.sh: test_expect_success 'is_descendant_of:hit' '
X:commit-1-1
9: 5a147a9704 ! 8: 8ec119edc6 commit-graph: use generation v2 only if entire chain does
@@ Commit message
commits in the lower layer before allowing the topo-order queue to write
anything to output (depending on the size of the upper layer).
+ When writing the new layer in split commit-graph, we write a GDAT chunk
+ only if the topmost layer has a GDAT chunk. This guarantees that if a
+ layer has GDAT chunk, all lower layers must have a GDAT chunk as well.
+
+ Rewriting layers follows similar approach: if the topmost layer below
+ the set of layers being rewritten (in the split commit-graph chain)
+ exists, and it does not contain GDAT chunk, then the result of rewrite
+ does not have GDAT chunks either.
+
Signed-off-by: Derrick Stolee <[email protected]>
Signed-off-by: Abhishek Kumar <[email protected]>
@@ commit-graph.c: static struct commit_graph *load_commit_graph_chain(struct repos
return graph_chain;
}
-+static void validate_mixed_generation_chain(struct repository *r)
++static void validate_mixed_generation_chain(struct commit_graph *g)
+{
-+ struct commit_graph *g = r->objects->commit_graph;
-+ int read_generation_data = 1;
++ int read_generation_data;
+
-+ while (g) {
-+ if (!g->chunk_generation_data) {
-+ read_generation_data = 0;
-+ break;
-+ }
-+ g = g->base_graph;
-+ }
++ if (!g)
++ return;
+
-+ g = r->objects->commit_graph;
++ read_generation_data = !!g->chunk_generation_data;
+
+ while (g) {
+ g->read_generation_data = read_generation_data;
@@ commit-graph.c: struct commit_graph *read_commit_graph_one(struct repository *r,
if (!g)
g = load_commit_graph_chain(r, odb);
-+ validate_mixed_generation_chain(r);
++ validate_mixed_generation_chain(g);
+
return g;
}
@@ commit-graph.c: static void fill_commit_graph_info(struct commit *item, struct c
date_low = get_be32(commit_data + g->hash_len + 12);
item->date = (timestamp_t)((date_high << 32) | date_low);
-- if (g->chunk_generation_data)
-+ if (g->chunk_generation_data && g->read_generation_data)
- graph_data->generation = item->date +
- (timestamp_t) get_be32(g->chunk_generation_data + sizeof(uint32_t) * lex_index);
- else
-@@ commit-graph.c: void load_commit_graph_info(struct repository *r, struct commit *item)
- uint32_t pos;
- if (!prepare_commit_graph(r))
- return;
+- if (g->chunk_generation_data) {
++ if (g->chunk_generation_data && g->read_generation_data) {
+ offset = (timestamp_t) get_be32(g->chunk_generation_data + sizeof(uint32_t) * lex_index);
+
+ if (offset & CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW) {
+@@ commit-graph.c: static void split_graph_merge_strategy(struct write_commit_graph_context *ctx)
+ }
+ }
+
++ if (!ctx->write_generation_data && g->chunk_generation_data)
++ ctx->write_generation_data = 1;
+
- if (find_commit_in_graph(item, r->objects->commit_graph, &pos))
- fill_commit_graph_info(item, r->objects->commit_graph, pos);
- }
+ if (flags != COMMIT_GRAPH_SPLIT_REPLACE)
+ ctx->new_base_graph = g;
+ else if (ctx->num_commit_graphs_after != 1)
+@@ commit-graph.c: int write_commit_graph(struct object_directory *odb,
+ struct commit_graph *g = ctx->r->objects->commit_graph;
+
+ while (g) {
++ g->read_generation_data = 1;
+ g->topo_levels = &topo_levels;
+ g = g->base_graph;
+ }
@@ commit-graph.c: int write_commit_graph(struct object_directory *odb,
g = ctx->r->objects->commit_graph;
@@ commit-graph.c: int write_commit_graph(struct object_directory *odb,
g = g->base_graph;
@@ commit-graph.c: int write_commit_graph(struct object_directory *odb,
- if (ctx->split_opts)
- replace = ctx->split_opts->flags & COMMIT_GRAPH_SPLIT_REPLACE;
+ if (ctx->opts)
+ replace = ctx->opts->split_flags & COMMIT_GRAPH_SPLIT_REPLACE;
+
+ if (replace)
+ ctx->write_generation_data = 1;
@@ commit-graph.h: struct commit_graph {
struct object_directory *odb;
uint32_t num_commits_in_base;
-+ uint32_t read_generation_data;
++ unsigned int read_generation_data;
struct commit_graph *base_graph;
const uint32_t *chunk_oid_fanout;
## t/t5324-split-commit-graph.sh ##
-@@ t/t5324-split-commit-graph.sh: done <<\EOF
- 0600 -r--------
- EOF
+@@ t/t5324-split-commit-graph.sh: test_expect_success '--split=replace with partial Bloom data' '
+ verify_chain_files_exist $graphdir
+ '
+test_expect_success 'setup repo for mixed generation commit-graph-chain' '
+ mkdir mixed &&
+ graphdir=".git/objects/info/commit-graphs" &&
++ test_oid_cache <<-EOM &&
++ oid_version sha1:1
++ oid_version sha256:2
++ EOM
+ cd "$TRASH_DIRECTORY/mixed" &&
+ git init &&
+ git config core.commitGraph true &&
@@ t/t5324-split-commit-graph.sh: done <<\EOF
+ GIT_TEST_COMMIT_GRAPH_NO_GDAT=1 git commit-graph write --reachable --split=no-merge &&
+ test-tool read-graph >output &&
+ cat >expect <<-EOF &&
-+ header: 43475048 1 1 4 1
++ header: 43475048 1 $(test_oid oid_version) 4 1
+ num_commits: 2
+ chunks: oid_fanout oid_lookup commit_metadata
+ EOF
@@ t/t5324-split-commit-graph.sh: done <<\EOF
+ git commit-graph write --reachable --split=no-merge &&
+ test-tool read-graph >output &&
+ cat >expect <<-EOF &&
-+ header: 43475048 1 1 4 2
++ header: 43475048 1 $(test_oid oid_version) 4 2
+ num_commits: 3
+ chunks: oid_fanout oid_lookup commit_metadata
+ EOF
@@ t/t5324-split-commit-graph.sh: done <<\EOF
+ graph_read_expect 15 &&
+ git commit-graph verify
+'
++
++test_expect_success 'add one commit, write a tip graph' '
++ cd "$TRASH_DIRECTORY/mixed" &&
++ test_commit 11 &&
++ git branch commits/11 &&
++ git commit-graph write --reachable --split &&
++ test_path_is_missing $infodir/commit-graph &&
++ test_path_is_file $graphdir/commit-graph-chain &&
++ ls $graphdir/graph-*.graph >graph-files &&
++ test_line_count = 2 graph-files &&
++ verify_chain_files_exist $graphdir
++'
+
test_done 10: 439adc1718 ! 9: bb9b02af32 commit-reach: use corrected commit dates in paint_down_to_common()
@@ Commit message
With corrected commit dates implemented, we no longer have to rely on commit date as a heuristic in paint_down_to_common().
- t6024-recursive-merge setups a unique repository where all commits have
- the same committer date without well-defined merge-base. As this has
- already caused problems (as noted in 859fdc0 (commit-graph: define
- GIT_TEST_COMMIT_GRAPH, 2018-08-29)), we disable commit graph within the
- test script.
+ While using corrected commit dates Git walks nearly the same number of
+ commits as commit date, the process is slower as for each comparision we
+ have to access a commit-slab (for corrected committer date) instead of
+ accessing struct member (for committer date).
+
+ For example, the command git merge-base v4.8 v4.9 on the linux
+ repository walks 167468 commits, taking 0.135s for committer date and
+ 167496 commits, taking 0.157s for corrected committer date respectively.
+
+ t6404-recursive-merge setups a unique repository where all commits have
+ the same committer date without well-defined merge-base.
+
+ While running tests with GIT_TEST_COMMIT_GRAPH unset, we use committer
+ date as a heuristic in paint_down_to_common(). 6404.1 'combined merge
+ conflicts' merges commits in the order:
+ - Merge C with B to form a intermediate commit.
+ - Merge the intermediate commit with A.
+
+ With GIT_TEST_COMMIT_GRAPH=1, we write a commit-graph and subsequently
+ use the corrected committer date, which changes the order in which
+ commits are merged:
+ - Merge A with B to form a intermediate commit.
+ - Merge the intermediate commit with C.
+
+ While resulting repositories are equivalent, 6404.4 'virtual trees were
+ processed' fails with GIT_TEST_COMMIT_GRAPH=1 as we are selecting
+ different merge-bases and thus have different object ids for the
+ intermediate commits.
+
+ As this has already causes problems (as noted in 859fdc0 (commit-graph:
+ define GIT_TEST_COMMIT_GRAPH, 2018-08-29)), we disable commit graph
+ within t6404-recursive-merge.
@@ commit-graph.c: int generation_numbers_enabled(struct repository *r)
+ if (!g->num_commits)
+ return 0;
+
-+ return !!g->chunk_generation_data;
++ return g->read_generation_data;
+}
+
- static void close_commit_graph_one(struct commit_graph *g)
+ struct bloom_filter_settings *get_bloom_filter_settings(struct repository *r) {
- if (!g)
+ struct commit_graph *g = r->objects->commit_graph;
## commit-graph.h ##
-@@ commit-graph.h: struct commit_graph *parse_commit_graph(void *graph_map, size_t graph_size); +@@ commit-graph.h: struct commit_graph *read_commit_graph_one(struct repository *r,
+ struct commit_graph *parse_commit_graph(struct repository *r,
+ void *graph_map, size_t graph_size);
+
++struct bloom_filter_settings *get_bloom_filter_settings(struct repository *r); ++
+ /*
+ * Return 1 if and only if the repository has a commit-graph
+ * file and generation numbers are computed in that file.
*/
int generation_numbers_enabled(struct repository *r);
+-struct bloom_filter_settings *get_bloom_filter_settings(struct repository *r);
+/*
+ * Return 1 if and only if the repository has a commit-graph
+ * file and generation data chunk has been written for the file.
+ */
+int corrected_commit_dates_enabled(struct repository *r);
-+
+
enum commit_graph_write_flags {
COMMIT_GRAPH_WRITE_APPEND = (1 << 0),
- COMMIT_GRAPH_WRITE_PROGRESS = (1 << 1),
## commit-reach.c ##
@@ commit-reach.c: static struct commit_list *paint_down_to_common(struct repository *r,
@@ commit-reach.c: static struct commit_list *paint_down_to_common(struct repositor
one->object.flags |= PARENT1;
- ## t/t6024-recursive-merge.sh ##
-@@ t/t6024-recursive-merge.sh: GIT_COMMITTER_DATE="2006-12-12 23:28:00 +0100"
+ ## t/t6404-recursive-merge.sh ##
+@@ t/t6404-recursive-merge.sh: GIT_COMMITTER_DATE="2006-12-12 23:28:00 +0100"
export GIT_COMMITTER_DATE
test_expect_success 'setup tests' '
@@ t/t6024-recursive-merge.sh: GIT_COMMITTER_DATE="2006-12-12 23:28:00 +0100"
echo 1 >a1 &&
git add a1 &&
GIT_AUTHOR_DATE="2006-12-12 23:00:00" git commit -m 1 a1 &&
-@@ t/t6024-recursive-merge.sh: test_expect_success 'setup tests' ' +@@ t/t6404-recursive-merge.sh: test_expect_success 'setup tests' '
'
test_expect_success 'combined merge conflicts' '
@@ t/t6024-recursive-merge.sh: test_expect_success 'setup tests' '
'
test_expect_success 'result contains a conflict' '
+@@ t/t6404-recursive-merge.sh: test_expect_success 'result contains a conflict' '
+ '
+
+ test_expect_success 'virtual trees were processed' '
++ # TODO: fragile test, relies on ambigious merge-base resolution
+ git ls-files --stage >out &&
+
+ cat >expect <<-EOF &&
11: f6f91af305 ! 10: 9ada43967d doc: add corrected commit date info
@@ Documentation/technical/commit-graph-format.txt: Git commit graph format
- The root tree OID.
@@ Documentation/technical/commit-graph-format.txt: CHUNK DATA:
+ position. If there are more than two parents, the second value
+ has its most-significant bit on and the other bits store an array
+ position into the Extra Edge List chunk.
+- * The next 8 bytes store the generation number of the commit and
++ * The next 8 bytes store the topological level (generation number v1)
++ of the commit and
+ the commit time in seconds since EPOCH. The generation number
+ uses the higher 30 bits of the first 4 bytes, while the commit
+ time uses the 32 bits of the second 4 bytes, along with the lowest
2 bits of the lowest byte, storing the 33rd and 34th bit of the commit time.
-+ Generation Data (ID: {'G', 'D', 'A', 'T' }) (N * 4 bytes) [Optional] ++ Generation Data (ID: {'G', 'D', 'A', 'T' }) (N * 4 bytes)
+ * This list of 4-byte values store corrected commit date offsets for the
+ commits, arranged in the same order as commit data chunk.
-+ * This list can be later modified to store future generation number related
-+ data.
++ * If the corrected commit date offset cannot be stored within 31 bits,
++ the value has its most-significant bit on and the other bits store
++ the position of corrected commit date into the Generation Data Overflow
++ chunk.
++
++ Generation Data Overflow (ID: {'G', 'D', 'O', 'V' }) [Optional]
++ * This list of 8-byte values stores the corrected commit dates for commits
++ with corrected commit date offsets that cannot be stored within 31 bits.
+
Extra Edge List (ID: {'E', 'D', 'G', 'E'}) [Optional]
This list of 4-byte values store the second through nth parents for
@@ Documentation/technical/commit-graph.txt: A consumer may load the following info
-Define the "generation number" of a commit recursively as follows:
+There are two definitions of generation number:
-+1. Corrected committer dates
-+2. Topological levels
-+
++1. Corrected committer dates (generation number v2)
++2. Topological levels (generation nummber v1)
+
+- * A commit with no parents (a root commit) has generation number one.
+Define "corrected committer date" of a commit recursively as follows:
-+
+
+- * A commit with at least one parent has generation number one more than
+- the largest generation number among its parents.
+ * A commit with no parents (a root commit) has corrected committer date
+ equal to its committer date.
-+
+
+-Equivalently, the generation number of a commit A is one more than the
+ * A commit with at least one parent has corrected committer date equal to
+ the maximum of its commiter date and one more than the largest corrected
+ committer date among its parents.
+
++ * As a special case, a root commit with timestamp zero has corrected commit
++ date of 1, to be able to distinguish it from GENERATION_NUMBER_ZERO
++ (that is, an uncomputed corrected commit date).
++
+Define the "topological level" of a commit recursively as follows:
-
- * A commit with no parents (a root commit) has generation number one. -
-- * A commit with at least one parent has generation number one more than
-- the largest generation number among its parents.
++
++ * A commit with no parents (a root commit) has topological level of one. ++
+ * A commit with at least one parent has topological level one more than
+ the largest topological level among its parents.
-
--Equivalently, the generation number of a commit A is one more than the ++
+Equivalently, the topological level of a commit A is one more than the
length of a longest path from A to a root commit. The recursive definition is easier to use for computation and observing the following property:
+@@ Documentation/technical/commit-graph.txt: is easier to use for computation and observing the following property:
+ generation numbers, then we always expand the boundary commit with highest
+ generation number and can easily detect the stopping condition.
+
++The properties applies to both versions of generation number, that is both
++corrected committer dates and topological levels.
++
+ This property can be used to significantly reduce the time it takes to
+ walk commits and determine topological relationships. Without generation
+ numbers, the general heuristic is the following:
@@ Documentation/technical/commit-graph.txt: numbers, the general heuristic is the following:
If A and B are commits with commit time X and Y, respectively, and
X < Y, then A _probably_ cannot reach B.
-This heuristic is currently used whenever the computation is allowed to
--violate topological relationships due to clock skew (such as "git log"
--with default order), but is not used when the topological order is
--required (such as merge base calculations, "git log --graph").
--
- In practice, we expect some commits to be created recently and not stored
- in the commit graph. We can treat these commits as having "infinite"
++In absence of corrected commit dates (for example, old versions of Git or
++mixed generation graph chains),
++this heuristic is currently used whenever the computation is allowed to
+ violate topological relationships due to clock skew (such as "git log"
+ with default order), but is not used when the topological order is
+ required (such as merge base calculations, "git log --graph").
+@@ Documentation/technical/commit-graph.txt: in the commit graph. We can treat these commits as having "infinite"
generation number and walk until reaching commits with known generation
number.
@@ Documentation/technical/commit-graph.txt: fully-computed generation numbers. Usi
with generation number *_INFINITY or *_ZERO is valuable.
-We use the macro GENERATION_NUMBER_MAX = 0x3FFFFFFF to for commits whose
--generation numbers are computed to be at least this value. We limit at
--this value since it is the largest value that can be stored in the
--commit-graph file using the 30 bits available to generation numbers. This
--presents another case where a commit can have generation number equal to
--that of a parent.
-+We use the macro GENERATION_NUMBER_MAX for commits whose generation numbers
-+are computed to be at least this value. We limit at this value since it is
-+the largest value that can be stored in the commit-graph file using the
-+available to generation numbers. This presents another case where a
-+commit can have generation number equal to that of a parent.
-
- Design Details
- --------------
++We use the macro GENERATION_NUMBER_MAX for commits whose
+ generation numbers are computed to be at least this value. We limit at
+ this value since it is the largest value that can be stored in the
+ commit-graph file using the 30 bits available to generation numbers. This
@@ Documentation/technical/commit-graph.txt: The merge strategy values (2 for the size multiple, 64,000 for the maximum
number of commits) could be extracted into config settings for full
flexibility.
-+We also merge commit-graph chains when we try to write a commit graph with -+two different generation number definitions as they cannot be compared directly. -+We overwrite the existing chain and create a commit-graph with the newer or more -+efficient defintion. For example, overwriting topological levels commit graph -+chain to create a corrected commit dates commit graph chain.
++## Handling Mixed Generation Number Chains
++
++With the introduction of generation number v2 and generation data chunk, the ++following scenario is possible:
++
++1. "New" Git writes a commit-graph with the corrected commit dates. ++2. "Old" Git writes a split commit-graph on top without corrected commit dates. ++
++A naive approach of using the newest available generation number from ++each layer would lead to violated expectations: the lower layer would ++use corrected commit dates which are much larger than the topological ++levels of the higher layer. For this reason, Git inspects each layer to ++see if any layer is missing corrected commit dates. In such a case, Git ++only uses topological level
++
++When writing a new layer in split commit-graph, we write corrected commit ++dates if the topmost layer has corrected commit dates written. This ++guarantees that if a layer has corrected commit dates, all lower layers ++must have corrected commit dates as well.
++
++When merging layers, we do not consider whether the merged layers had corrected ++commit dates. Instead, the new layer will have corrected commit dates if and ++only if all existing layers below the new layer have corrected commit dates.
+
## Deleting graph-{hash} files
Signature · 1 line
gitgitgadget
PATCH v4 1/10 commit-graph: fix regression when computing Bloom filters
From: Abhishek Kumar <[email protected]>
commit_gen_cmp is used when writing a commit-graph to sort commits in generation order before computing Bloom filters. Since c49c82aa (commit:
move members graph_pos, generation to a slab, 2020-06-17) made it so that 'commit_graph_generation()' returns 'GENERATION_NUMBER_INFINITY' during writing, we cannot call it within this function. Instead, access the generation number directly through the slab (i.e., by calling 'commit_graph_data_at(c)->generation') in order to access it while writing.
While measuring performance with git commit-graph write --reachable --changed-paths on the linux repository led to around 1m40s for both HEAD and master (and could be due to fault in my measurements), it is still the "right" thing to do.
--- commit-graph.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
commit-graph.c +2 -2
@@ -144,8 +144,8 @@ static int commit_gen_cmp(const void *va, const void *vb)const struct commit *a = *(const struct commit **)va;const struct commit *b = *(const struct commit **)vb; -uint32_t generation_a =commit_graph_generation (a); -uint32_t generation_b =commit_graph_generation (b); +uint32_t generation_a =commit_graph_data_at (a)->generation; +uint32_t generation_b =commit_graph_data_at (b)->generation;/* lower generation commits first */ if (generation_a < generation_b)return -1 ;
Signature · 1 line
gitgitgadget
Re: [PATCH v4 01/10] commit-graph: fix regression when computing Bloom filters
Quoted 10 lines · Abhishek Kumar via GitGitGadget
From: Abhishek Kumar <[email protected]>
commit_gen_cmpis used when writing a commit-graph to sort commits in generation order before computing Bloom filters. Sincec49c82aa(commit:
move membersgraph_pos, generation to a slab, 2020-06-17) made it so that 'commit_graph_generation()' returns 'GENERATION_NUMBER_INFINITY' during writing, we cannot call it within this function. Instead, access the generation number directly through the slab (i.e., by calling 'commit_graph_data_at(c)->generation') in order to access it while writing.
This description is all right, but I think it can be made more clear:
When running git commit-graph write --reachable --changed-paths to compute Bloom filters for changed paths, commits are first sorted by generation number using 'commit_gen_cmp()'. Commits with similar generation are more likely to have many trees in common, making the diff faster, see 3d112755.
However, since c49c82aa (commit: move members graph_pos, generation to a slab, 2020-06-17) made it so that 'commit_graph_generation()' returns 'GENERATION_NUMBER_INFINITY' during writing, we cannot call it within this function. Instead, access the generation number directly through the slab (i.e., by calling 'commit_graph_data_at(c)->generation') in order to access it while writing.
Or something like that.
We should also add an explanation why avoiding getter is safe here, perhaps adding the following line to the second paragraph:
It is safe to do because 'commit_gen_cmp()' from commit-graph.c is static and used only when writing Bloom filters, and because writing changed-paths filters is done after computing generation numbers (if necessary).
Or something like that.
Quoted 5 lines
While measuring performance with
git commit-graph write --reachable --changed-pathson the linux repository led to around 1m40s for both HEAD and master (and could be due to fault in my measurements), it is still the "right" thing to do.
I had to read the above paragraph several times to understand it, possibly because I have expected here to be a fix for a performance regression. The commit message for 3d112755 (commit-graph: examine commits by generation number) describes reduction of computation time from 3m00s to 1m37s. So I would expect performance with HEAD (i.e. before those changes) to be around 3m, not the same before and after changes being around 1m40s.
Can anyone recheck this before-and-after benchmark, please?
Anyway, it might be more clear to write it as the following:
On the Linux kernel repository, this patch didn't reduce the computation time for 'git commit-graph write --reachable --changed-paths', which is around 1m40s both before and after this change. This could be a fault in my measurements; it is still the "right" thing to do.
Or something like that.
Quoted 2 lines
Signed-off-by Abhishek Kumar <[email protected]>---
Anyway, it is nice and clear change.
Quoted 8 lines
commit-graph.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)commit-graph.c +0
-0
commit-graph.c static int commit_gen_cmp(const void *va, const void *vb) · line 151
Quoted 10 lines
commit-graph.c +2
-2@@ -144,8 +144,8 @@ static int commit_gen_cmp(const void *va, const void *vb)const struct commit *a = *(const struct commit **)va;const struct commit *b = *(const struct commit **)vb; -uint32_t generation_a =commit_graph_generation (a); -uint32_t generation_b =commit_graph_generation (b); +uint32_t generation_a =commit_graph_data_at (a)->generation; +uint32_t generation_b =commit_graph_data_at (b)->generation;/* lower generation commits first */ if (generation_a < generation_b)return -1 ;
Best,
Signature · 1 line
Jakub Narębski
Re: [PATCH v4 01/10] commit-graph: fix regression when computing Bloom filters
Quoted 15 lines · Jakub Narębski
Quoted 4 lines · Abhishek Kumar via GitGitGadget
While measuring performance with
git commit-graph write --reachable --changed-pathson the linux repository led to around 1m40s for both HEAD and master (and could be due to fault in my measurements), it is still the "right" thing to do.I had to read the above paragraph several times to understand it, possibly because I have expected here to be a fix for a performance regression. The commit message for
3d112755(commit-graph: examine commits by generation number) describes reduction of computation time from 3m00s to 1m37s. So I would expect performance with HEAD (i.e. before those changes) to be around 3m, not the same before and after changes being around 1m40s.
Quoted 1 line
Can anyone recheck this before-and-after benchmark, please?
My hunch is that our heuristic to fall back to the commits 'date' value is saving us here. commit_gen_cmp() first compares the generation numbers, breaking ties by 'date' as a heuristic. But since all generation number queries return GENERATION_NUMBER_INFINITY during writing, we're relying on our heuristic entirely.
I haven't looked much further than that, other than to see that I could get about a ~4sec speed-up with this patch as compared to v2.29.1 in the computing Bloom filters region on the kernel.
Quoted 9 lines
Anyway, it might be more clear to write it as the following:
On the Linux kernel repository, this patch didn't reduce the computation time for 'git commit-graph write --reachable --changed-paths', which is around 1m40s both before and after this change. This could be a fault in my measurements; it is still the "right" thing to do.
Or something like that.
Assuming that we are in fact being saved by the "date" heuristic, I'd probably write the following commit message instead:
Before computing Bloom filters, the commit-graph machinery uses commit_gen_cmp to sort commits by generation order for improved diff performance. 3d11275505 (commit-graph: examine commits by generation number, 2020-03-30) claims that this sort can reduce the time spent to compute Bloom filters by nearly half.
But since c49c82aa4c (commit: move members graph_pos, generation to a slab, 2020-06-17), this optimization is broken, since asking for 'commit_graph_generation()' directly returns GENERATION_NUMBER_INFINITY while writing.
Not all hope is lost, though: 'commit_graph_generation()' falls back to comparing commits by their date when they have equal generation number, and so since c49c82aa4c is purely a date comparison function. This heuristic is good enough that we don't seem to loose appreciable performance while computing Bloom filters. [Benchmark that we loose about ~4sec before/after c49c82aa4c9...]
So, avoid the uesless 'commit_graph_generation()' while writing by instead accessing the slab directly. This returns the newly-computed generation numbers, and allows us to avoid the heuristic by directly comparing generation numbers.
Thanks,
Taylor
Re: [PATCH v4 01/10] commit-graph: fix regression when computing Bloom filters
Quoted 24 lines · Taylor Blau
Quoted 15 lines · Jakub Narębski
Quoted 4 lines · Abhishek Kumar via GitGitGadget
While measuring performance with
git commit-graph write --reachable --changed-pathson the linux repository led to around 1m40s for both HEAD and master (and could be due to fault in my measurements), it is still the "right" thing to do.I had to read the above paragraph several times to understand it, possibly because I have expected here to be a fix for a performance regression. The commit message for
3d112755(commit-graph: examine commits by generation number) describes reduction of computation time from 3m00s to 1m37s. So I would expect performance with HEAD (i.e. before those changes) to be around 3m, not the same before and after changes being around 1m40s.Quoted 1 line
Can anyone recheck this before-and-after benchmark, please?
My hunch is that our heuristic to fall back to the commits 'date' value is saving us here.
commit_gen_cmp()first compares the generation numbers, breaking ties by 'date' as a heuristic. But since all generation number queries returnGENERATION_NUMBER_INFINITYduring writing, we're relying on our heuristic entirely.
Quoted 4 lines
I haven't looked much further than that, other than to see that I could get about a ~4sec speed-up with this patch as compared to v2.29.1 in the computing Bloom filters region on the kernel.
Thanks for benchmarking it. I wasn't sure if I am testing it correctly or the patch made no difference.
Quoted 31 lines
Quoted 9 lines
Anyway, it might be more clear to write it as the following:
On the Linux kernel repository, this patch didn't reduce the computation time for 'git commit-graph write --reachable --changed-paths', which is around 1m40s both before and after this change. This could be a fault in my measurements; it is still the "right" thing to do.
Or something like that.
Assuming that we are in fact being saved by the "date" heuristic, I'd probably write the following commit message instead:
Before computing Bloom filters, the commit-graph machinery uses
commit_gen_cmpto sort commits by generation order for improved diff performance.3d11275505(commit-graph: examine commits by generation number, 2020-03-30) claims that this sort can reduce the time spent to compute Bloom filters by nearly half.But since
c49c82aa4c(commit: move membersgraph_pos, generation to a slab, 2020-06-17), this optimization is broken, since asking for 'commit_graph_generation()' directly returnsGENERATION_NUMBER_INFINITYwhile writing.Not all hope is lost, though: '
commit_graph_generation()' falls back to comparing commits by their date when they have equal generation number, and so sincec49c82aa4cis purely a date comparison function. This heuristic is good enough that we don't seem to loose appreciable performance while computing Bloom filters. [Benchmark that we loose about ~4sec before/afterc49c82aa4c9...]
Quoted 5 lines
So, avoid the uesless '
commit_graph_generation()' while writing by instead accessing the slab directly. This returns the newly-computed generation numbers, and allows us to avoid the heuristic by directly comparing generation numbers.
That's a lot better, will change.
Quoted 2 lines
Thanks,
Taylor
PATCH v4 2/10 revision: parse parent in indegree_walk_step()
In reply to Abhishek Kumar via GitGitGadgetFrom: Abhishek Kumar <[email protected]>
In indegree_walk_step(), we add unvisited parents to the indegree queue. However, parents are not guaranteed to be parsed. As the indegree queue sorts by generation number, let's parse parents before inserting them to ensure the correct priority order.
--- revision.c | 3 +++ 1 file changed, 3 insertions(+)
revision.c +3 -0
@@ -3381,6 +3381,9 @@ static void indegree_walk_step(struct rev_info *revs)struct commit *parent = p->item;int *pi =indegree_slab_at (&info->indegree, parent); +if (repo_parse_commit_gently (revs->repo, parent,1 ) <0 ) +return ; +if (*pi) (*pi)++;else
Signature · 1 line
gitgitgadget
Re: [PATCH v4 02/10] revision: parse parent in indegree_walk_step()
Quoted 6 lines · Abhishek Kumar via GitGitGadget
From: Abhishek Kumar <[email protected]>
In
indegree_walk_step(), we add unvisited parents to the indegree queue. However, parents are not guaranteed to be parsed. As the indegree queue sorts by generation number, let's parse parents before inserting them to ensure the correct priority order.
All right, we need to ensure the parent commit is parsed to know its generation number, to insert in into priority queue in a correct order.
Quoted 2 lines
Signed-off-by Abhishek Kumar <[email protected]>
Looks good.
Quoted 9 lines
--- revision.c | 3 +++ 1 file changed, 3 insertions(+)revision.c +0
-0
revision.c static void indegree_walk_step(struct rev_info *revs) · line 3389
Quoted 9 lines
revision.c +3
-0@@ -3381,6 +3381,9 @@ static void indegree_walk_step(struct rev_info *revs)struct commit *parent = p->item;int *pi =indegree_slab_at (&info->indegree, parent); +if (repo_parse_commit_gently (revs->repo, parent,1 ) <0 ) +return ; +if (*pi) (*pi)++;else
Best,
Signature · 1 line
Jakub Narębski
PATCH v4 3/10 commit-graph: consolidate fill_commit_graph_info
In reply to Abhishek Kumar via GitGitGadgetFrom: Abhishek Kumar <[email protected]>
Both fill_commit_graph_info() and fill_commit_in_graph() parse information present in commit data chunk. Let's simplify the implementation by calling fill_commit_graph_info() within fill_commit_in_graph().
fill_commit_graph_info() used to not load committer data from commit data chunk. However, with the corrected committer date, we have to load committer date to calculate generation number value.
e51217e15 (t5000: test tar files that overflow ustar headers, 30-06-2016) introduced a test 'generate tar with future mtime' that creates a commit with committer date of (2 ^ 36 + 1) seconds since EPOCH. The CDAT chunk provides 34-bits for storing committer date, thus committer time overflows into generation number (within CDAT chunk) and has undefined behavior.
The test used to pass as fill_commit_graph_info() would not set struct member date of struct commit and loads committer date from the object database, generating a tar file with the expected mtime.
However, with corrected commit date, we will load the committer date from CDAT chunk (truncated to lower 34-bits to populate the generation number. Thus, Git sets date and generates tar file with the truncated mtime.
The ustar format (the header format used by most modern tar programs) only has room for 11 (or 12, depending om some implementations) octal digits for the size and mtime of each files.
Thus, setting a timestamp of 2 ^ 33 + 1 would overflow the 11-octal digit implementations while still fitting into commit data chunk.
Since we want to test 12-octal digit implementations of ustar as well, let's modify the existing test to no longer use commit-graph file.
--- commit-graph.c | 27 ++++++++++----------------- t/t5000-tar-tree.sh | 20 +++++++++++++++++++- 2 files changed, 29 insertions(+), 18 deletions(-)
commit-graph.c +10 -17
@@ -749,15 +749,24 @@ static void fill_commit_graph_info(struct commit *item, struct commit_graph *g,const unsigned char *commit_data;struct commit_graph_data *graph_data;uint32_t lex_index; +uint64_t date_high, date_low;while (pos < g->num_commits_in_base) g = g->base_graph; +if (pos >= g->num_commits + g->num_commits_in_base) +die (_ ("invalid commit position. commit-graph is likely corrupt" )); + lex_index = pos - g->num_commits_in_base; commit_data = g->chunk_commit_data +GRAPH_DATA_WIDTH * lex_index; graph_data =commit_graph_data_at (item); graph_data->graph_pos = pos; + + date_high =get_be32 (commit_data + g->hash_len +8 ) &0x3 ; + date_low =get_be32 (commit_data + g->hash_len +12 ); + item->date = (timestamp_t)((date_high <<32 ) | date_low); + graph_data->generation =get_be32 (commit_data + g->hash_len +8 ) >>2 ; }
@@ -772,38 +781,22 @@ static int fill_commit_in_graph(struct repository *r, {uint32_t edge_value;uint32_t *parent_data_ptr; -uint64_t date_low, date_high;struct commit_list **pptr; -struct commit_graph_data *graph_data;const unsigned char *commit_data;uint32_t lex_index;while (pos < g->num_commits_in_base) g = g->base_graph; -if (pos >= g->num_commits + g->num_commits_in_base) -die (_ ("invalid commit position. commit-graph is likely corrupt" )); +fill_commit_graph_info (item, g, pos); -/* -* Store the "full" position, but then use the -* "local" position for the rest of the calculation. -*/ - graph_data =commit_graph_data_at (item); - graph_data->graph_pos = pos; lex_index = pos - g->num_commits_in_base; - commit_data = g->chunk_commit_data + (g->hash_len +16 ) * lex_index; item->object.parsed =1 ;set_commit_tree (item,NULL ); - date_high =get_be32 (commit_data + g->hash_len +8 ) &0x3 ; - date_low =get_be32 (commit_data + g->hash_len +12 ); - item->date = (timestamp_t)((date_high <<32 ) | date_low); - - graph_data->generation =get_be32 (commit_data + g->hash_len +8 ) >>2 ; - pptr = &item->parents; edge_value =get_be32 (commit_data + g->hash_len);
t/t5000-tar-tree.sh +19 -1
@@ -431,11 +431,29 @@ test_expect_success TAR_HUGE,LONG_IS_64BIT 'system tar can read our huge size' ' test_cmp expect actual ' +test_expect_success TIME_IS_64BIT 'set up repository with far-future commit' ' + rm -f .git/index && + echo foo >file && + git add file && + GIT_COMMITTER_DATE="@17179869183 +0000" \ + git commit -m "tempori parendum" +' + +test_expect_success TIME_IS_64BIT 'generate tar with future mtime' ' + git archive HEAD >future.tar +' + +test_expect_success TAR_HUGE,TIME_IS_64BIT,TIME_T_IS_64BIT 'system tar can read our future mtime' ' + echo 2514 >expect && + tar_info future.tar | cut -d" " -f2 >actual && + test_cmp expect actual +' + test_expect_success TIME_IS_64BIT 'set up repository with far-future commit' ' rm -f .git/index && echo content >file && git add file && - GIT_COMMITTER_DATE="@68719476737 +0000" \ + GIT_TEST_COMMIT_GRAPH=0 GIT_COMMITTER_DATE="@68719476737 +0000" \ git commit -m "tempori parendum" '
Signature · 1 line
gitgitgadget
Re: [PATCH v4 03/10] commit-graph: consolidate fill_commit_graph_info
Hi Abhishek,
In short: everything is all right, except for the now duplicated test names in t5000 after this commit.
Quoted 10 lines · Abhishek Kumar via GitGitGadget
From: Abhishek Kumar <[email protected]>
Both
fill_commit_graph_info()andfill_commit_in_graph()parse information present in commit data chunk. Let's simplify the implementation by callingfill_commit_graph_info()withinfill_commit_in_graph().
fill_commit_graph_info()used to not load committer data from commit data chunk. However, with the corrected committer date, we have to load committer date to calculate generation number value.
Nice writeup, however the last sentence would in my opinion read better in the future tense: we don't use generation number v2 yet. For example:
However, with upcoming switch to using corrected committer date as generation number v2, we will have to load committer date to compute generation number value anyway.
Or something like that - notice the minor addition and changes.
The following is slightly unrelated change, but we agreed that it would be better to not separate them; the need for change to the t5000 test is caused by the change described above.
Quoted 11 lines
e51217e15(t5000: test tar files that overflow ustar headers, 30-06-2016) introduced a test 'generate tar with future mtime' that creates a commit with committer date of (2 ^ 36 + 1) seconds since EPOCH. The CDAT chunk provides 34-bits for storing committer date, thus committer time overflows into generation number (within CDAT chunk) and has undefined behavior.The test used to pass as
fill_commit_graph_info()would not set struct memberdateof struct commit and loads committer date from the object database, generating a tar file with the expected mtime.
I think it should be s/loads/load/, as in "would load", but I am not a native English speaker.
Quoted 10 lines
However, with corrected commit date, we will load the committer date from CDAT chunk (truncated to lower 34-bits to populate the generation number. Thus, Git sets date and generates tar file with the truncated mtime.
The ustar format (the header format used by most modern tar programs) only has room for 11 (or 12, depending om some implementations) octal digits for the size and mtime of each files.
Quoted 5 lines
Thus, setting a timestamp of 2 ^ 33 + 1 would overflow the 11-octal digit implementations while still fitting into commit data chunk.
Since we want to test 12-octal digit implementations of ustar as well, let's modify the existing test to no longer use commit-graph file.
The description above is for me does not make it entirely clear that we add new test for handling possible 11-octal digit overflow nearly identical to the existing one, and turn off use of commit-graph file for test that checks handling 12-octal digit overflow.
Quoted 65 lines
Signed-off-by Abhishek Kumar <[email protected]>--- commit-graph.c | 27 ++++++++++----------------- t/t5000-tar-tree.sh | 20 +++++++++++++++++++- 2 files changed, 29 insertions(+), 18 deletions(-)commit-graph.c +10
-11@@ -749,15 +749,24 @@ static void fill_commit_graph_info(struct commit *item, struct commit_graph *g,const unsigned char *commit_data;struct commit_graph_data *graph_data;uint32_t lex_index; +uint64_t date_high, date_low;while (pos < g->num_commits_in_base) g = g->base_graph; +if (pos >= g->num_commits + g->num_commits_in_base) +die (_ ("invalid commit position. commit-graph is likely corrupt" )); + lex_index = pos - g->num_commits_in_base; commit_data = g->chunk_commit_data +GRAPH_DATA_WIDTH * lex_index; graph_data =commit_graph_data_at (item); graph_data->graph_pos = pos; + + date_high =get_be32 (commit_data + g->hash_len +8 ) &0x3 ; + date_low =get_be32 (commit_data + g->hash_len +12 ); + item->date = (timestamp_t)((date_high <<32 ) | date_low); + graph_data->generation =get_be32 (commit_data + g->hash_len +8 ) >>2 ; }@@ -772,38 +781,22 @@ static int fill_commit_in_graph(struct repository *r, {uint32_t edge_value;uint32_t *parent_data_ptr; -uint64_t date_low, date_high;struct commit_list **pptr; -struct commit_graph_data *graph_data;const unsigned char *commit_data;uint32_t lex_index;while (pos < g->num_commits_in_base) g = g->base_graph; -if (pos >= g->num_commits + g->num_commits_in_base) -die (_ ("invalid commit position. commit-graph is likely corrupt" )); +fill_commit_graph_info (item, g, pos); -/* -* Store the "full" position, but then use the -* "local" position for the rest of the calculation. -*/ - graph_data =commit_graph_data_at (item); - graph_data->graph_pos = pos; lex_index = pos - g->num_commits_in_base; - commit_data = g->chunk_commit_data + (g->hash_len +16 ) * lex_index; item->object.parsed =1 ;set_commit_tree (item,NULL );
commit-graph.c static int fill_commit_in_graph(struct repository *r, · line 802
Quoted 10 lines
commit-graph.c +0
-6- date_high =get_be32 (commit_data + g->hash_len +8 ) &0x3 ; - date_low =get_be32 (commit_data + g->hash_len +12 ); - item->date = (timestamp_t)((date_high <<32 ) | date_low); - - graph_data->generation =get_be32 (commit_data + g->hash_len +8 ) >>2 ; - pptr = &item->parents; edge_value =get_be32 (commit_data + g->hash_len);
All right, looks good for me.
Here second change begins.
Quoted 16 lines
t/t5000-tar-tree.sh +8
-0@@ -431,11 +431,29 @@ test_expect_success TAR_HUGE,LONG_IS_64BIT 'system tar can read our huge size' ' test_cmp expect actual ' +test_expect_success TIME_IS_64BIT 'set up repository with far-future commit' ' + rm -f .git/index && + echo foo >file && + git add file && + GIT_COMMITTER_DATE="@17179869183 +0000" \ + git commit -m "tempori parendum" +' +
t/t5000-tar-tree.sh test_expect_success TAR_HUGE,LONG_IS_64BIT 'system tar can read our huge size' ' · line 451
Quoted 10 lines
t/t5000-tar-tree.sh +10
-0+test_expect_success TIME_IS_64BIT 'generate tar with future mtime' ' + git archive HEAD >future.tar +' + +test_expect_success TAR_HUGE,TIME_IS_64BIT,TIME_T_IS_64BIT 'system tar can read our future mtime' ' + echo 2514 >expect && + tar_info future.tar | cut -d" " -f2 >actual && + test_cmp expect actual +' +
Everything is all right, except we now have duplicated test names.
Perhaps in the three following tests we should use 'far-far-future commit' and 'far future mtime' in place of current 'far-future commit' and 'future mtime' for tests checking handling 12-digital ditgits overflow, or add description how far the future is, for example 'far-future commit (2^11 + 1)', etc.
t/t5000-tar-tree.sh test_expect_success TAR_HUGE,LONG_IS_64BIT 'system tar can read our huge size' ' · line 458
Quoted 8 lines
t/t5000-tar-tree.sh +1
-1test_expect_success TIME_IS_64BIT 'set up repository with far-future commit' ' rm -f .git/index && echo content >file && git add file && - GIT_COMMITTER_DATE="@68719476737 +0000" \ + GIT_TEST_COMMIT_GRAPH=0 GIT_COMMITTER_DATE="@68719476737 +0000" \ git commit -m "tempori parendum" '
Best,
Signature · 1 line
Jakub Narębski
Re: [PATCH v4 03/10] commit-graph: consolidate fill_commit_graph_info
Hello Dr. Narębski,
Quoted 22 lines · Jakub Narębski
Hi Abhishek,
In short: everything is all right, except for the now duplicated test names in t5000 after this commit.
Quoted 10 lines · Abhishek Kumar via GitGitGadget
From: Abhishek Kumar <[email protected]>
Both
fill_commit_graph_info()andfill_commit_in_graph()parse information present in commit data chunk. Let's simplify the implementation by callingfill_commit_graph_info()withinfill_commit_in_graph().
fill_commit_graph_info()used to not load committer data from commit data chunk. However, with the corrected committer date, we have to load committer date to calculate generation number value.Nice writeup, however the last sentence would in my opinion read better in the future tense: we don't use generation number v2 yet. For example:
Quoted 6 lines
However, with upcoming switch to using corrected committer date as generation number v2, we will have to load committer date to compute generation number value anyway.
Or something like that - notice the minor addition and changes.
Thanks for the change, it looks better!
Quoted 13 lines
The following is slightly unrelated change, but we agreed that it would be better to not separate them; the need for change to the t5000 test is caused by the change described above.
Quoted 8 lines
e51217e15(t5000: test tar files that overflow ustar headers, 30-06-2016) introduced a test 'generate tar with future mtime' that creates a commit with committer date of (2 ^ 36 + 1) seconds since EPOCH. The CDAT chunk provides 34-bits for storing committer date, thus committer time overflows into generation number (within CDAT chunk) and has undefined behavior.
Quoted 7 lines
Quoted 3 lines
The test used to pass as
fill_commit_graph_info()would not set struct memberdateof struct commit and loads committer date from the object database, generating a tar file with the expected mtime.I think it should be s/loads/load/, as in "would load", but I am not a native English speaker.
That's correct - since I have used "would not set" in the first half of sentence, the later half should follow suit too.
Quoted 13 lines
Quoted 13 lines
However, with corrected commit date, we will load the committer date from CDAT chunk (truncated to lower 34-bits to populate the generation number. Thus, Git sets date and generates tar file with the truncated mtime.
The ustar format (the header format used by most modern tar programs) only has room for 11 (or 12, depending om some implementations) octal digits for the size and mtime of each files.
Thus, setting a timestamp of 2 ^ 33 + 1 would overflow the 11-octal digit implementations while still fitting into commit data chunk.
Quoted 8 lines
Quoted 2 lines
Since we want to test 12-octal digit implementations of ustar as well, let's modify the existing test to no longer use commit-graph file.
The description above is for me does not make it entirely clear that we add new test for handling possible 11-octal digit overflow nearly identical to the existing one, and turn off use of commit-graph file for test that checks handling 12-octal digit overflow.
Revised the last paragraphs to:
The ustar format (the header format used by most modern tar programs) only has room for 11 (or 12, depending on some implementations) octal digits for the size and mtime of each file.
To test the 11-octal digit implementation, we create a future commit with committer date of 2^34 - 1, which overflows 11-octal digits without overflowing 34-bits of the Commit Data chunk.
To test the 12-octal digit implementation, the smallest committer date possible is 2^36, which overflows the Commit Data chunk and thus commit-graph must be disabled for the test.
Quoted 109 lines
Quoted 65 lines
Signed-off-by Abhishek Kumar <[email protected]>--- commit-graph.c | 27 ++++++++++----------------- t/t5000-tar-tree.sh | 20 +++++++++++++++++++- 2 files changed, 29 insertions(+), 18 deletions(-)commit-graph.c +10
-11@@ -749,15 +749,24 @@ static void fill_commit_graph_info(struct commit *item, struct commit_graph *g,const unsigned char *commit_data;struct commit_graph_data *graph_data;uint32_t lex_index; +uint64_t date_high, date_low;while (pos < g->num_commits_in_base) g = g->base_graph; +if (pos >= g->num_commits + g->num_commits_in_base) +die (_ ("invalid commit position. commit-graph is likely corrupt" )); + lex_index = pos - g->num_commits_in_base; commit_data = g->chunk_commit_data +GRAPH_DATA_WIDTH * lex_index; graph_data =commit_graph_data_at (item); graph_data->graph_pos = pos; + + date_high =get_be32 (commit_data + g->hash_len +8 ) &0x3 ; + date_low =get_be32 (commit_data + g->hash_len +12 ); + item->date = (timestamp_t)((date_high <<32 ) | date_low); + graph_data->generation =get_be32 (commit_data + g->hash_len +8 ) >>2 ; }@@ -772,38 +781,22 @@ static int fill_commit_in_graph(struct repository *r, {uint32_t edge_value;uint32_t *parent_data_ptr; -uint64_t date_low, date_high;struct commit_list **pptr; -struct commit_graph_data *graph_data;const unsigned char *commit_data;uint32_t lex_index;while (pos < g->num_commits_in_base) g = g->base_graph; -if (pos >= g->num_commits + g->num_commits_in_base) -die (_ ("invalid commit position. commit-graph is likely corrupt" )); +fill_commit_graph_info (item, g, pos); -/* -* Store the "full" position, but then use the -* "local" position for the rest of the calculation. -*/ - graph_data =commit_graph_data_at (item); - graph_data->graph_pos = pos; lex_index = pos - g->num_commits_in_base; - commit_data = g->chunk_commit_data + (g->hash_len +16 ) * lex_index; item->object.parsed =1 ;set_commit_tree (item,NULL );commit-graph.c static int fill_commit_in_graph(struct repository *r, · line 802
Quoted 10 lines
commit-graph.c +0
-6- date_high =get_be32 (commit_data + g->hash_len +8 ) &0x3 ; - date_low =get_be32 (commit_data + g->hash_len +12 ); - item->date = (timestamp_t)((date_high <<32 ) | date_low); - - graph_data->generation =get_be32 (commit_data + g->hash_len +8 ) >>2 ; - pptr = &item->parents; edge_value =get_be32 (commit_data + g->hash_len);All right, looks good for me.
Here second change begins.
Quoted 16 lines
t/t5000-tar-tree.sh +8
-0@@ -431,11 +431,29 @@ test_expect_success TAR_HUGE,LONG_IS_64BIT 'system tar can read our huge size' ' test_cmp expect actual ' +test_expect_success TIME_IS_64BIT 'set up repository with far-future commit' ' + rm -f .git/index && + echo foo >file && + git add file && + GIT_COMMITTER_DATE="@17179869183 +0000" \ + git commit -m "tempori parendum" +' +t/t5000-tar-tree.sh test_expect_success TAR_HUGE,LONG_IS_64BIT 'system tar can read our huge size' ' · line 451
Quoted 10 lines
t/t5000-tar-tree.sh +10
-0+test_expect_success TIME_IS_64BIT 'generate tar with future mtime' ' + git archive HEAD >future.tar +' + +test_expect_success TAR_HUGE,TIME_IS_64BIT,TIME_T_IS_64BIT 'system tar can read our future mtime' ' + echo 2514 >expect && + tar_info future.tar | cut -d" " -f2 >actual && + test_cmp expect actual +' +Everything is all right, except we now have duplicated test names.
Quoted 6 lines
Perhaps in the three following tests we should use 'far-far-future commit' and 'far future mtime' in place of current 'far-future commit' and 'future mtime' for tests checking handling 12-digital ditgits overflow, or add description how far the future is, for example 'far-future commit (2^11 + 1)', etc.
Changed, thanks for pointing this out.
Quoted 12 lines
t/t5000-tar-tree.sh test_expect_success TAR_HUGE,LONG_IS_64BIT 'system tar can read our huge size' ' · line 458
Quoted 8 lines
t/t5000-tar-tree.sh +1
-1test_expect_success TIME_IS_64BIT 'set up repository with far-future commit' ' rm -f .git/index && echo content >file && git add file && - GIT_COMMITTER_DATE="@68719476737 +0000" \ + GIT_TEST_COMMIT_GRAPH=0 GIT_COMMITTER_DATE="@68719476737 +0000" \ git commit -m "tempori parendum" 'Best,
Signature · 1 line
Jakub Narębski
Thanks
- Abhishek
PATCH v4 4/10 commit-graph: return 64-bit generation number
In reply to Abhishek Kumar via GitGitGadgetFrom: Abhishek Kumar <[email protected]>
In a preparatory step, let's return timestamp_t values from commit_graph_generation(), use timestamp_t for local variables and define GENERATION_NUMBER_INFINITY as (2 ^ 63 - 1) instead.
We rename GENERATION_NUMBER_MAX to GENERATION_NUMBER_V1_MAX to represent the largest topological level we can store in the commit data chunk.
With corrected commit dates implemented, we will have two such *_MAX variables to denote the largest offset and largest topological level that can be stored.
--- commit-graph.c | 22 +++++++++++----------- commit-graph.h | 4 ++-- commit-reach.c | 36 ++++++++++++++++++------------------ commit-reach.h | 2 +- commit.c | 4 ++-- commit.h | 4 ++-- revision.c | 10 +++++----- upload-pack.c | 2 +- 8 files changed, 42 insertions(+), 42 deletions(-)
commit-graph.c +11 -11
@@ -99,7 +99,7 @@ uint32_t commit_graph_position(const struct commit *c)return data ? data->graph_pos :COMMIT_NOT_FROM_GRAPH ; } -uint32_t commit_graph_generation (const struct commit *c) +timestamp_t commit_graph_generation (const struct commit *c) {struct commit_graph_data *data =commit_graph_data_slab_peek (&commit_graph_data_slab, c);
@@ -144,8 +144,8 @@ static int commit_gen_cmp(const void *va, const void *vb)const struct commit *a = *(const struct commit **)va;const struct commit *b = *(const struct commit **)vb; -uint32_t generation_a =commit_graph_data_at (a)->generation; -uint32_t generation_b =commit_graph_data_at (b)->generation; +const timestamp_t generation_a =commit_graph_data_at (a)->generation; +const timestamp_t generation_b =commit_graph_data_at (b)->generation;/* lower generation commits first */ if (generation_a < generation_b)return -1 ;
@@ -1350,7 +1350,7 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx)_ ("Computing commit graph generation numbers" ), ctx->commits.nr);for (i =0 ; i < ctx->commits.nr; i++) { -uint32_t generation =commit_graph_data_at (ctx->commits.list[i])->generation; +timestamp_t generation =commit_graph_data_at (ctx->commits.list[i])->generation;display_progress (ctx->progress, i +1 ); if (generation != GENERATION_NUMBER_INFINITY &&
@@ -1383,8 +1383,8 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx) data->generation = max_generation +1 ;pop_commit (&list); -if (data->generation >GENERATION_NUMBER_MAX ) - data->generation =GENERATION_NUMBER_MAX ; +if (data->generation >GENERATION_NUMBER_V1_MAX ) + data->generation =GENERATION_NUMBER_V1_MAX ; } } }
@@ -2404,8 +2404,8 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags)for (i =0 ; i < g->num_commits; i++) {struct commit *graph_commit, *odb_commit;struct commit_list *graph_parents, *odb_parents; -uint32_t max_generation =0 ; -uint32_t generation; +timestamp_t max_generation =0 ; +timestamp_t generation;display_progress (progress, i +1 );hashcpy (cur_oid.hash, g->chunk_oid_lookup + g->hash_len * i);
@@ -2469,11 +2469,11 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags)continue ;/* -* If one of our parents has generation GENERATION_NUMBER_MAX, then -* our generation is also GENERATION_NUMBER_MAX. Decrement to avoid +* If one of our parents has generation GENERATION_NUMBER_V1_MAX, then +* our generation is also GENERATION_NUMBER_V1_MAX. Decrement to avoid * extra logic in the following condition. */ -if (max_generation ==GENERATION_NUMBER_MAX ) +if (max_generation ==GENERATION_NUMBER_V1_MAX ) max_generation--; generation =commit_graph_generation (graph_commit);
commit-graph.h +2 -2
@@ -144,12 +144,12 @@ void disable_commit_graph(struct repository *r);struct commit_graph_data {uint32_t graph_pos; -uint32_t generation; +timestamp_t generation; };/* * Commits should be parsed before accessing generation, graph positions. */ -uint32_t commit_graph_generation (const struct commit *); +timestamp_t commit_graph_generation (const struct commit *);uint32_t commit_graph_position (const struct commit *);#endif
commit-reach.c +18 -18
@@ -32,12 +32,12 @@ static int queue_has_nonstale(struct prio_queue *queue)static struct commit_list *paint_down_to_common (struct repository *r,struct commit *one,int n,struct commit **twos, -int min_generation) +timestamp_t min_generation) {struct prio_queue queue = { compare_commits_by_gen_then_commit_date };struct commit_list *result =NULL ;int i; -uint32_t last_gen =GENERATION_NUMBER_INFINITY ; +timestamp_t last_gen =GENERATION_NUMBER_INFINITY ;if (!min_generation) queue.compare = compare_commits_by_commit_date;
@@ -58,10 +58,10 @@ static struct commit_list *paint_down_to_common(struct repository *r,struct commit *commit =prio_queue_get (&queue);struct commit_list *parents;int flags; -uint32_t generation =commit_graph_generation (commit); +timestamp_t generation =commit_graph_generation (commit);if (min_generation && generation > last_gen) -BUG ("bad generation skip %8x > %8x at %s" , +BUG ("bad generation skip %" PRItime" > %" PRItime" at %s" , generation, last_gen,oid_to_hex (&commit->object.oid)); last_gen = generation;
@@ -177,12 +177,12 @@ static int remove_redundant(struct repository *r, struct commit **array, int cntrepo_parse_commit (r, array[i]);for (i =0 ; i < cnt; i++) {struct commit_list *common; -uint32_t min_generation =commit_graph_generation (array[i]); +timestamp_t min_generation =commit_graph_generation (array[i]);if (redundant[i])continue ;for (j = filled =0 ; j < cnt; j++) { -uint32_t curr_generation; +timestamp_t curr_generation;if (i == j || redundant[j])continue ; filled_index[filled] = j;
@@ -321,7 +321,7 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit, {struct commit_list *bases;int ret =0 , i; -uint32_t generation, max_generation =GENERATION_NUMBER_ZERO ; +timestamp_t generation, max_generation =GENERATION_NUMBER_INFINITY ;if (repo_parse_commit (r, commit))return ret;
@@ -470,7 +470,7 @@ static int in_commit_list(const struct commit_list *want, struct commit *c)static enum contains_result contains_test (struct commit *candidate,const struct commit_list *want,struct contains_cache *cache, -uint32_t cutoff) +timestamp_t cutoff) {enum contains_result *cached =contains_cache_at (cache, candidate);
@@ -506,11 +506,11 @@ static enum contains_result contains_tag_algo(struct commit *candidate, {struct contains_stack contains_stack = {0 ,0 ,NULL };enum contains_result result; -uint32_t cutoff =GENERATION_NUMBER_INFINITY ; +timestamp_t cutoff =GENERATION_NUMBER_INFINITY ;const struct commit_list *p;for (p = want; p; p = p->next) { -uint32_t generation; +timestamp_t generation;struct commit *c = p->item;load_commit_graph_info (the_repository, c); generation =commit_graph_generation (c);
@@ -566,8 +566,8 @@ static int compare_commits_by_gen(const void *_a, const void *_b)const struct commit *a = *(const struct commit *const *)_a;const struct commit *b = *(const struct commit *const *)_b; -uint32_t generation_a =commit_graph_generation (a); -uint32_t generation_b =commit_graph_generation (b); +timestamp_t generation_a =commit_graph_generation (a); +timestamp_t generation_b =commit_graph_generation (b);if (generation_a < generation_b)return -1 ;
@@ -580,7 +580,7 @@ int can_all_from_reach_with_flag(struct object_array *from,unsigned int with_flag, unsigned int assign_flag, time_t min_commit_date, - uint32_t min_generation) + timestamp_t min_generation) { struct commit **list = NULL;int i;
@@ -681,13 +681,13 @@ int can_all_from_reach(struct commit_list *from, struct commit_list *to,time_t min_commit_date = cutoff_by_min_date ? from->item->date :0 ;struct commit_list *from_iter = from, *to_iter = to;int result; -uint32_t min_generation =GENERATION_NUMBER_INFINITY ; +timestamp_t min_generation =GENERATION_NUMBER_INFINITY ;while (from_iter) {add_object_array (&from_iter->item->object,NULL , &from_objs);if (!parse_commit (from_iter->item)) { -uint32_t generation; +timestamp_t generation;if (from_iter->item->date < min_commit_date) min_commit_date = from_iter->item->date;
@@ -701,7 +701,7 @@ int can_all_from_reach(struct commit_list *from, struct commit_list *to,while (to_iter) {if (!parse_commit (to_iter->item)) { -uint32_t generation; +timestamp_t generation;if (to_iter->item->date < min_commit_date) min_commit_date = to_iter->item->date;
@@ -741,13 +741,13 @@ struct commit_list *get_reachable_subset(struct commit **from, int nr_from,struct commit_list *found_commits =NULL ;struct commit **to_last = to + nr_to;struct commit **from_last = from + nr_from; -uint32_t min_generation =GENERATION_NUMBER_INFINITY ; +timestamp_t min_generation =GENERATION_NUMBER_INFINITY ;int num_to_find =0 ;struct prio_queue queue = { compare_commits_by_gen_then_commit_date };for (item = to; item < to_last; item++) { -uint32_t generation; +timestamp_t generation;struct commit *c = *item;parse_commit (c);
commit-reach.h +1 -1
@@ -87,7 +87,7 @@ int can_all_from_reach_with_flag(struct object_array *from,unsigned int with_flag, unsigned int assign_flag, time_t min_commit_date, - uint32_t min_generation); + timestamp_t min_generation);int can_all_from_reach (struct commit_list *from,struct commit_list *to,int commit_date_cutoff);
commit.c +2 -2
@@ -731,8 +731,8 @@ int compare_commits_by_author_date(const void *a_, const void *b_,int compare_commits_by_gen_then_commit_date (const void *a_,const void *b_,void *unused) {const struct commit *a = a_, *b = b_; -const uint32_t generation_a =commit_graph_generation (a), - generation_b =commit_graph_generation (b); +const timestamp_t generation_a =commit_graph_generation (a), + generation_b =commit_graph_generation (b);/* newer commits first */ if (generation_a < generation_b)
commit.h +2 -2
@@ -11,8 +11,8 @@#include "commit-slab.h" #define COMMIT_NOT_FROM_GRAPH 0xFFFFFFFF -#define GENERATION_NUMBER_INFINITY 0xFFFFFFFF -#define GENERATION_NUMBER_MAX 0x3FFFFFFF +#define GENERATION_NUMBER_INFINITY ((1ULL << 63) - 1) +#define GENERATION_NUMBER_V1_MAX 0x3FFFFFFF#define GENERATION_NUMBER_ZERO 0struct commit_list {
revision.c +5 -5
@@ -3308,7 +3308,7 @@ define_commit_slab(indegree_slab, int);define_commit_slab (author_date_slab, timestamp_t);struct topo_walk_info { -uint32_t min_generation; +timestamp_t min_generation;struct prio_queue explore_queue;struct prio_queue indegree_queue;struct prio_queue topo_queue;
@@ -3354,7 +3354,7 @@ static void explore_walk_step(struct rev_info *revs) }static void explore_to_depth (struct rev_info *revs, -uint32_t gen_cutoff) +timestamp_t gen_cutoff) {struct topo_walk_info *info = revs->topo_walk_info;struct commit *c;
@@ -3397,7 +3397,7 @@ static void indegree_walk_step(struct rev_info *revs) }static void compute_indegrees_to_depth (struct rev_info *revs, -uint32_t gen_cutoff) +timestamp_t gen_cutoff) {struct topo_walk_info *info = revs->topo_walk_info;struct commit *c;
@@ -3455,7 +3455,7 @@ static void init_topo_walk(struct rev_info *revs) info->min_generation =GENERATION_NUMBER_INFINITY ;for (list = revs->commits; list; list = list->next) {struct commit *c = list->item; -uint32_t generation; +timestamp_t generation;if (repo_parse_commit_gently (revs->repo, c,1 ))continue ;
@@ -3516,7 +3516,7 @@ static void expand_topo_walk(struct rev_info *revs, struct commit *commit)for (p = commit->parents; p; p = p->next) {struct commit *parent = p->item;int *pi; -uint32_t generation; +timestamp_t generation;if (parent->object.flags &UNINTERESTING )continue ;
upload-pack.c +1 -1
@@ -497,7 +497,7 @@ static int got_oid(struct upload_pack_data *data,static int ok_to_give_up (struct upload_pack_data *data) { -uint32_t min_generation =GENERATION_NUMBER_ZERO ; +timestamp_t min_generation =GENERATION_NUMBER_ZERO ;if (!data->have_obj.nr)return 0 ;
Signature · 1 line
gitgitgadget
Re: [PATCH v4 04/10] commit-graph: return 64-bit generation number
Hi Abhishek,
Quoted 5 lines · Abhishek Kumar via GitGitGadget
From: Abhishek Kumar <[email protected]>
In a preparatory step, let's return
timestamp_tvalues fromcommit_graph_generation(), usetimestamp_tfor local variables and defineGENERATION_NUMBER_INFINITYas (2 ^ 63 - 1) instead.
I think it would be easier to understand if it was explicitely said what this preparatory step prepares for, e.g.:
In a preparatory step for introducing corrected commit dates as generation number, let's return timestamp_t values from...
Or even
generation number, let's change the return type of commit_graph_generation() to timestamp_t, and use ...
Otherwise it looks good.
Quoted 8 lines
We rename
GENERATION_NUMBER_MAXtoGENERATION_NUMBER_V1_MAXto represent the largest topological level we can store in the commit data chunk.With corrected commit dates implemented, we will have two such
*_MAXvariables to denote the largest offset and largest topological level that can be stored.
All right, nice explanation.
Quoted 2 lines
Signed-off-by Abhishek Kumar <[email protected]>
Note that there are two changes that are not mentioned in the commit message, namely adding 'const'-ness to generation_a/b local variables in commit_gen_cmp() from commit-graph.c, and switching from GENERATION_NUMBER_ZERO to GENERATION_NUMBER_INFINITY as the default (initial) value for 'max_generation' in repo_in_merge_bases_many().
While the former is a simple "while-at-it" change that shouldn't affect correctness, the latter needs an explanation (or fixing if it is wrong).
Quoted 16 lines
--- commit-graph.c | 22 +++++++++++----------- commit-graph.h | 4 ++-- commit-reach.c | 36 ++++++++++++++++++------------------ commit-reach.h | 2 +- commit.c | 4 ++-- commit.h | 4 ++-- revision.c | 10 +++++----- upload-pack.c | 2 +- 8 files changed, 42 insertions(+), 42 deletions(-)commit-graph.c +0
-0
commit-graph.c uint32_t commit_graph_position(const struct commit *c) · line 102
Quoted 5 lines
commit-graph.c +1
-1@@ -99,7 +99,7 @@ uint32_t commit_graph_position(const struct commit *c)return data ? data->graph_pos :COMMIT_NOT_FROM_GRAPH ; } -uint32_t commit_graph_generation (const struct commit *c) +timestamp_t commit_graph_generation (const struct commit *c)
All right.
commit-graph.c static int commit_gen_cmp(const void *va, const void *vb) · line 148
Quoted 11 lines
commit-graph.c +2
-2{struct commit_graph_data *data =commit_graph_data_slab_peek (&commit_graph_data_slab, c);@@ -144,8 +144,8 @@ static int commit_gen_cmp(const void *va, const void *vb)const struct commit *a = *(const struct commit **)va;const struct commit *b = *(const struct commit **)vb; -uint32_t generation_a =commit_graph_data_at (a)->generation; -uint32_t generation_b =commit_graph_data_at (b)->generation; +const timestamp_t generation_a =commit_graph_data_at (a)->generation; +const timestamp_t generation_b =commit_graph_data_at (b)->generation;
All right... but this also adds 'const' qualifier. I understand that you don't want to create separate commit for this "while at it" change...
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1353
Quoted 9 lines
commit-graph.c +1
-1/* lower generation commits first */ if (generation_a < generation_b)return -1 ;@@ -1350,7 +1350,7 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx)_ ("Computing commit graph generation numbers" ), ctx->commits.nr);for (i =0 ; i < ctx->commits.nr; i++) { -uint32_t generation =commit_graph_data_at (ctx->commits.list[i])->generation; +timestamp_t generation =commit_graph_data_at (ctx->commits.list[i])->generation;
All right.
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1387
Quoted 11 lines
commit-graph.c +2
-2display_progress (ctx->progress, i +1 ); if (generation != GENERATION_NUMBER_INFINITY &&@@ -1383,8 +1383,8 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx) data->generation = max_generation +1 ;pop_commit (&list); -if (data->generation >GENERATION_NUMBER_MAX ) - data->generation =GENERATION_NUMBER_MAX ; +if (data->generation >GENERATION_NUMBER_V1_MAX ) + data->generation =GENERATION_NUMBER_V1_MAX ;
All right, this is the other mentioned change.
commit-graph.c int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags) · line 2408
Quoted 11 lines
commit-graph.c +2
-2} } }@@ -2404,8 +2404,8 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags)for (i =0 ; i < g->num_commits; i++) {struct commit *graph_commit, *odb_commit;struct commit_list *graph_parents, *odb_parents; -uint32_t max_generation =0 ; -uint32_t generation; +timestamp_t max_generation =0 ; +timestamp_t generation;
All right.
Quoted 6 lines
commit-graph.c +0
-0display_progress (progress, i +1 );hashcpy (cur_oid.hash, g->chunk_oid_lookup + g->hash_len * i);@@ -2469,11 +2469,11 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags)continue ;
commit-graph.c int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags) · line 2477
Quoted 10 lines
commit-graph.c +3
-3/* -* If one of our parents has generation GENERATION_NUMBER_MAX, then -* our generation is also GENERATION_NUMBER_MAX. Decrement to avoid +* If one of our parents has generation GENERATION_NUMBER_V1_MAX, then +* our generation is also GENERATION_NUMBER_V1_MAX. Decrement to avoid * extra logic in the following condition. */ -if (max_generation ==GENERATION_NUMBER_MAX ) +if (max_generation ==GENERATION_NUMBER_V1_MAX ) max_generation--;
All right. Nice fixing a comment too.
commit-graph.h void disable_commit_graph(struct repository *r); · line 148
Quoted 13 lines
commit-graph.c +0
-0generation =commit_graph_generation (graph_commit);commit-graph.h +1
-1@@ -144,12 +144,12 @@ void disable_commit_graph(struct repository *r);struct commit_graph_data {uint32_t graph_pos; -uint32_t generation; +timestamp_t generation; };
All right.
commit-graph.h void disable_commit_graph(struct repository *r); · line 155
Quoted 8 lines
commit-graph.h +1
-1/* * Commits should be parsed before accessing generation, graph positions. */ -uint32_t commit_graph_generation (const struct commit *); +timestamp_t commit_graph_generation (const struct commit *);uint32_t commit_graph_position (const struct commit *);#endif
All right.
Quoted 6 lines
commit-reach.c +0
-0@@ -32,12 +32,12 @@ static int queue_has_nonstale(struct prio_queue *queue) static struct commit_list *paint_down_to_common(struct repository *r,
commit-reach.c static int queue_has_nonstale(struct prio_queue *queue) · line 40
Quoted 10 lines
commit-reach.c +2
-2struct commit *one, int n, struct commit **twos, - int min_generation) + timestamp_t min_generation) { struct prio_queue queue = { compare_commits_by_gen_then_commit_date };struct commit_list *result =NULL ;int i; -uint32_t last_gen =GENERATION_NUMBER_INFINITY ; +timestamp_t last_gen =GENERATION_NUMBER_INFINITY ;
All right.
commit-reach.c static struct commit_list *paint_down_to_common(struct repository *r, · line 61
Quoted 9 lines
commit-reach.c +1
-1if (!min_generation) queue.compare = compare_commits_by_commit_date;@@ -58,10 +58,10 @@ static struct commit_list *paint_down_to_common(struct repository *r,struct commit *commit =prio_queue_get (&queue);struct commit_list *parents;int flags; -uint32_t generation =commit_graph_generation (commit); +timestamp_t generation =commit_graph_generation (commit);
All right.
commit-reach.c static struct commit_list *paint_down_to_common(struct repository *r, · line 64
Quoted 4 lines
commit-reach.c +1
-1if (min_generation && generation > last_gen) - BUG("bad generation skip %8x > %8x at %s", + BUG("bad generation skip %"PRItime" > %"PRItime" at %s",
All right; nice of you noticing this issue.
Quoted 8 lines
commit-reach.c +0
-1generation, last_gen, oid_to_hex(&commit->object.oid)); last_gen = generation;@@ -177,12 +177,12 @@ static int remove_redundant(struct repository *r, struct commit **array, int cntrepo_parse_commit (r, array[i]);for (i =0 ; i < cnt; i++) {struct commit_list *common; -uint32_t min_generation =commit_graph_generation (array[i]);
commit-reach.c static int remove_redundant(struct repository *r, struct commit **array, int cnt · line 188
Quoted 10 lines
commit-reach.c +2
-1+timestamp_t min_generation =commit_graph_generation (array[i]);if (redundant[i])continue ;for (j = filled =0 ; j < cnt; j++) { -uint32_t curr_generation; +timestamp_t curr_generation;if (i == j || redundant[j])continue ; filled_index[filled] = j;
All right.
commit-reach.c int repo_in_merge_bases_many(struct repository *r, struct commit *commit, · line 324
Quoted 6 lines
commit-reach.c +1
-1@@ -321,7 +321,7 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit, {struct commit_list *bases;int ret =0 , i; -uint32_t generation, max_generation =GENERATION_NUMBER_ZERO ; +timestamp_t generation, max_generation =GENERATION_NUMBER_INFINITY ;
The change of type from uint32_t to timestamp_t is expected, but the change from GENERATION_NUMBER_ZERO to GENERATION_NUMBER_INFINITY is not.
This might be caused by the fact that repo_in_merge_bases_many() switched from using min_generation and GENERATION_NUMBER_INFINITY to using max_generation and GENERATION_NUMBER_ZERO. Or the reverse: I see one version on https://github.com/git/git, and other version in 'master' pulled from https://github.com/git-for-windows/git
Certainly max_generation should be paired with GENERATION_NUMBER_ZERO, and min_generation with GENERATION_NUMBER_INFINITY.
commit-reach.c static int in_commit_list(const struct commit_list *want, struct commit *c) · line 473
Quoted 9 lines
commit-reach.c +1
-1if (repo_parse_commit (r, commit))return ret;@@ -470,7 +470,7 @@ static int in_commit_list(const struct commit_list *want, struct commit *c)static enum contains_result contains_test (struct commit *candidate,const struct commit_list *want,struct contains_cache *cache, -uint32_t cutoff) +timestamp_t cutoff)
All right.
Sidenote: this parameter should probably be named gen_cutoff, for consistency and better readability (but that was the existing state), but this would also mean more changes.
commit-reach.c static enum contains_result contains_tag_algo(struct commit *candidate, · line 509
Quoted 9 lines
commit-reach.c +1
-1{enum contains_result *cached =contains_cache_at (cache, candidate);@@ -506,11 +506,11 @@ static enum contains_result contains_tag_algo(struct commit *candidate, {struct contains_stack contains_stack = {0 ,0 ,NULL };enum contains_result result; -uint32_t cutoff =GENERATION_NUMBER_INFINITY ; +timestamp_t cutoff =GENERATION_NUMBER_INFINITY ;
Sidenote: this variable should probably be named gen_cutoff, for consistency and better readability (but that was the existing state). However changing it would pollute this commit with unrelated changes; it is not that big of an isseu that it *requires* fixing.
commit-reach.c static enum contains_result contains_tag_algo(struct commit *candidate, · line 516
Quoted 8 lines
commit-reach.c +1
-1const struct commit_list *p;for (p = want; p; p = p->next) { -uint32_t generation; +timestamp_t generation;struct commit *c = p->item;load_commit_graph_info (the_repository, c); generation =commit_graph_generation (c);
All right.
commit-reach.c static int compare_commits_by_gen(const void *_a, const void *_b) · line 570
Quoted 8 lines
commit-reach.c +2
-2@@ -566,8 +566,8 @@ static int compare_commits_by_gen(const void *_a, const void *_b)const struct commit *a = *(const struct commit *const *)_a;const struct commit *b = *(const struct commit *const *)_b; -uint32_t generation_a =commit_graph_generation (a); -uint32_t generation_b =commit_graph_generation (b); +timestamp_t generation_a =commit_graph_generation (a); +timestamp_t generation_b =commit_graph_generation (b);
All right.
commit-reach.c int can_all_from_reach_with_flag(struct object_array *from, · line 586
Quoted 12 lines
commit-reach.c +1
-1if (generation_a < generation_b)return -1 ;@@ -580,7 +580,7 @@ int can_all_from_reach_with_flag(struct object_array *from,unsigned int with_flag, unsigned int assign_flag, time_t min_commit_date, - uint32_t min_generation) + timestamp_t min_generation) { struct commit **list = NULL;int i;
All right.
Quoted 6 lines
commit-reach.c +1
-1@@ -681,13 +681,13 @@ int can_all_from_reach(struct commit_list *from, struct commit_list *to,time_t min_commit_date = cutoff_by_min_date ? from->item->date :0 ;struct commit_list *from_iter = from, *to_iter = to;int result; -uint32_t min_generation =GENERATION_NUMBER_INFINITY ; +timestamp_t min_generation =GENERATION_NUMBER_INFINITY ;
commit-reach.c int can_all_from_reach(struct commit_list *from, struct commit_list *to, · line 693
Quoted 10 lines
commit-reach.c +1
-1while (from_iter) {add_object_array (&from_iter->item->object,NULL , &from_objs);if (!parse_commit (from_iter->item)) { -uint32_t generation; +timestamp_t generation;if (from_iter->item->date < min_commit_date) min_commit_date = from_iter->item->date;
All right.
commit-reach.c int can_all_from_reach(struct commit_list *from, struct commit_list *to, · line 707
Quoted 9 lines
commit-reach.c +1
-1@@ -701,7 +701,7 @@ int can_all_from_reach(struct commit_list *from, struct commit_list *to,while (to_iter) {if (!parse_commit (to_iter->item)) { -uint32_t generation; +timestamp_t generation;if (to_iter->item->date < min_commit_date) min_commit_date = to_iter->item->date;
All right.
Quoted 6 lines
commit-reach.c +1
-1@@ -741,13 +741,13 @@ struct commit_list *get_reachable_subset(struct commit **from, int nr_from,struct commit_list *found_commits =NULL ;struct commit **to_last = to + nr_to;struct commit **from_last = from + nr_from; -uint32_t min_generation =GENERATION_NUMBER_INFINITY ; +timestamp_t min_generation =GENERATION_NUMBER_INFINITY ;
commit-reach.c struct commit_list *get_reachable_subset(struct commit **from, int nr_from, · line 753
Quoted 10 lines
commit-reach.c +1
-1int num_to_find =0 ;struct prio_queue queue = { compare_commits_by_gen_then_commit_date };for (item = to; item < to_last; item++) { -uint32_t generation; +timestamp_t generation;struct commit *c = *item;parse_commit (c);
All right.
commit-reach.h int can_all_from_reach_with_flag(struct object_array *from, · line 93
Quoted 13 lines
commit-reach.h +1
-1@@ -87,7 +87,7 @@ int can_all_from_reach_with_flag(struct object_array *from,unsigned int with_flag, unsigned int assign_flag, time_t min_commit_date, - uint32_t min_generation); + timestamp_t min_generation);int can_all_from_reach (struct commit_list *from,struct commit_list *to,int commit_date_cutoff);
All right.
commit.c int compare_commits_by_author_date(const void *a_, const void *b_, · line 736
Quoted 13 lines
commit.c +2
-2@@ -731,8 +731,8 @@ int compare_commits_by_author_date(const void *a_, const void *b_,int compare_commits_by_gen_then_commit_date (const void *a_,const void *b_,void *unused) {const struct commit *a = a_, *b = b_; -const uint32_t generation_a =commit_graph_generation (a), - generation_b =commit_graph_generation (b); +const timestamp_t generation_a =commit_graph_generation (a), + generation_b =commit_graph_generation (b);
All right (assuming that the indent after change looks all right; but even if it doesn't t would be a very minor issue).
Quoted 7 lines
commit.c +0
-0/* newer commits first */ if (generation_a < generation_b)commit.h +0
-0
commit.h line 17
Quoted 9 lines
commit.h +2
-2@@ -11,8 +11,8 @@#include "commit-slab.h" #define COMMIT_NOT_FROM_GRAPH 0xFFFFFFFF -#define GENERATION_NUMBER_INFINITY 0xFFFFFFFF -#define GENERATION_NUMBER_MAX 0x3FFFFFFF +#define GENERATION_NUMBER_INFINITY ((1ULL << 63) - 1) +#define GENERATION_NUMBER_V1_MAX 0x3FFFFFFF#define GENERATION_NUMBER_ZERO 0
All right, we redefine GENERATION_NUMBER_INFINITY and rename GENERATION_NUMBER_MAX.
Quoted 6 lines
commit.h +0
-0struct commit_list {revision.c +0
-0
revision.c define_commit_slab(indegree_slab, int); · line 3314
Quoted 8 lines
revision.c +1
-1@@ -3308,7 +3308,7 @@ define_commit_slab(indegree_slab, int);define_commit_slab (author_date_slab, timestamp_t);struct topo_walk_info { -uint32_t min_generation; +timestamp_t min_generation;struct prio_queue explore_queue;struct prio_queue indegree_queue;struct prio_queue topo_queue;
All right.
revision.c static void explore_walk_step(struct rev_info *revs) · line 3360
Quoted 9 lines
revision.c +1
-1@@ -3354,7 +3354,7 @@ static void explore_walk_step(struct rev_info *revs) }static void explore_to_depth (struct rev_info *revs, -uint32_t gen_cutoff) +timestamp_t gen_cutoff) {struct topo_walk_info *info = revs->topo_walk_info;struct commit *c;
All right.
revision.c static void indegree_walk_step(struct rev_info *revs) · line 3403
Quoted 9 lines
revision.c +1
-1@@ -3397,7 +3397,7 @@ static void indegree_walk_step(struct rev_info *revs) }static void compute_indegrees_to_depth (struct rev_info *revs, -uint32_t gen_cutoff) +timestamp_t gen_cutoff) {struct topo_walk_info *info = revs->topo_walk_info;struct commit *c;
All right.
revision.c static void init_topo_walk(struct rev_info *revs) · line 3461
Quoted 9 lines
revision.c +1
-1@@ -3455,7 +3455,7 @@ static void init_topo_walk(struct rev_info *revs) info->min_generation =GENERATION_NUMBER_INFINITY ;for (list = revs->commits; list; list = list->next) {struct commit *c = list->item; -uint32_t generation; +timestamp_t generation;if (repo_parse_commit_gently (revs->repo, c,1 ))continue ;
All right.
revision.c static void expand_topo_walk(struct rev_info *revs, struct commit *commit) · line 3522
Quoted 9 lines
revision.c +1
-1@@ -3516,7 +3516,7 @@ static void expand_topo_walk(struct rev_info *revs, struct commit *commit)for (p = commit->parents; p; p = p->next) {struct commit *parent = p->item;int *pi; -uint32_t generation; +timestamp_t generation;if (parent->object.flags &UNINTERESTING )continue ;
All right.
upload-pack.c static int got_oid(struct upload_pack_data *data, · line 503
Quoted 13 lines
upload-pack.c +1
-1@@ -497,7 +497,7 @@ static int got_oid(struct upload_pack_data *data,static int ok_to_give_up (struct upload_pack_data *data) { -uint32_t min_generation =GENERATION_NUMBER_ZERO ; +timestamp_t min_generation =GENERATION_NUMBER_ZERO ;if (!data->have_obj.nr)return 0 ;
All right.
The only thing to check is if you have changed the type in all the places that need it. My cursory examination shows that those are all places than need fixing.
Note that the 'generation' variable in git-name-rev, git-fsck and in git-show-branch (snd sha1-name.c) means something different.
Also, 'first_generation' variable in generation_numbers_enabled() (part of commit-graph.c) examines and will examine generation number v1 i.e. topological levels, and do not need type change... though it may require name change in some time in the future; the generation number computation path also does not require change type, though variables would be renamed in the future commit.
Best,
Signature · 1 line
Jakub Narębski
Re: [PATCH v4 04/10] commit-graph: return 64-bit generation number
Quoted 11 lines · Jakub Narębski
Hi Abhishek,
Note that there are two changes that are not mentioned in the commit message, namely adding 'const'-ness to generation_a/b local variables in
commit_gen_cmp()fromcommit-graph.c, and switching fromGENERATION_NUMBER_ZEROtoGENERATION_NUMBER_INFINITYas the default (initial) value for 'max_generation' inrepo_in_merge_bases_many().While the former is a simple "while-at-it" change that shouldn't affect correctness, the latter needs an explanation (or fixing if it is wrong).
The change from GENERATION_NUMBER_ZERO to GENERATION_NUMBER_INFINITY was incorrect. While fixing merge conflicts on rebasing to master again, I didn't notice that repo_in_merge_bases_many() switched from using min_generation and GENERATION_NUMBER_ZERO to max_generation and GENERATION_NUMBER_ZERO.
Thanks for noticing!
Quoted 5 lines
...
Best,
Signature · 1 line
Jakub Narębski
PATCH v4 5/10 commit-graph: add a slab to store topological levels
In reply to Abhishek Kumar via GitGitGadgetFrom: Abhishek Kumar <[email protected]>
In a later commit we will introduce corrected commit date as the generation number v2. This value will be stored in the new seperate Generation Data chunk. However, to ensure backwards compatibility with "Old" Git we need to continue to write generation number v1, which is topological level, to the commit data chunk. This means that we need to compute both versions of generation numbers when writing the commit-graph file. Therefore, let's introduce a commit-slab to store topological levels; corrected commit date will be stored in the member generation of struct commit_graph_data.
When Git creates a split commit-graph, it takes advantage of the generation values that have been computed already and present in existing commit-graph files.
So, let's add a pointer to struct commit_graph as well as struct write_commit_graph_context to the topological level commit-slab and populate it with topological levels while writing a commit-graph file.
--- commit-graph.c | 47 ++++++++++++++++++++++++++++++++--------------- commit-graph.h | 1 + 2 files changed, 33 insertions(+), 15 deletions(-)
commit-graph.c +32 -15
@@ -64,6 +64,8 @@ void git_test_write_commit_graph_or_die(void)/* Remember to update object flag allocation in object.h */ #define REACHABLE (1u<<15) +define_commit_slab (topo_level_slab, uint32_t); +/* Keep track of the order in which commits are added to our list. */ define_commit_slab (commit_pos, int);static struct commit_pos commit_pos =COMMIT_SLAB_INIT (1 , commit_pos);
@@ -768,6 +770,9 @@ static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, item->date = (timestamp_t)((date_high <<32 ) | date_low); graph_data->generation =get_be32 (commit_data + g->hash_len +8 ) >>2 ; + +if (g->topo_levels) + *topo_level_slab_at (g->topo_levels, item) =get_be32 (commit_data + g->hash_len +8 ) >>2 ; }static inline void set_commit_tree (struct commit *c,struct tree *t)
@@ -962,6 +967,7 @@ struct write_commit_graph_context { changed_paths:1 , order_by_pack:1; +struct topo_level_slab *topo_levels;const struct commit_graph_opts *opts;size_t total_bloom_filter_data_size;const struct bloom_filter_settings *bloom_settings;
@@ -1108,7 +1114,7 @@ static int write_graph_chunk_data(struct hashfile *f,else packedDate[0 ] =0 ; - packedDate[0 ] |=htonl (commit_graph_data_at (*list)->generation <<2 ); + packedDate[0 ] |=htonl (*topo_level_slab_at (ctx->topo_levels, *list) <<2 ); packedDate[1 ] =htonl ((*list)->date);hashwrite (f, packedDate,8 );
@@ -1350,11 +1356,11 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx)_ ("Computing commit graph generation numbers" ), ctx->commits.nr);for (i =0 ; i < ctx->commits.nr; i++) { -timestamp_t generation =commit_graph_data_at (ctx->commits.list[i])->generation; +timestamp_t level = *topo_level_slab_at (ctx->topo_levels, ctx->commits.list[i]);display_progress (ctx->progress, i +1 ); -if (generation !=GENERATION_NUMBER_INFINITY && - generation !=GENERATION_NUMBER_ZERO ) +if (level !=GENERATION_NUMBER_INFINITY && + level !=GENERATION_NUMBER_ZERO )continue ;commit_list_insert (ctx->commits.list[i], &list);
@@ -1362,29 +1368,27 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx)struct commit *current = list->item;struct commit_list *parent;int all_parents_computed =1 ; -uint32_t max_generation =0 ; +uint32_t max_level =0 ;for (parent = current->parents; parent; parent = parent->next) { - generation =commit_graph_data_at (parent->item)->generation; + level = *topo_level_slab_at (ctx->topo_levels, parent->item); -if (generation ==GENERATION_NUMBER_INFINITY || - generation ==GENERATION_NUMBER_ZERO ) { +if (level ==GENERATION_NUMBER_INFINITY || + level ==GENERATION_NUMBER_ZERO ) { all_parents_computed =0 ;commit_list_insert (parent->item, &list);break ; - }else if (generation > max_generation) { - max_generation = generation; + }else if (level > max_level) { + max_level = level; } }if (all_parents_computed) { -struct commit_graph_data *data =commit_graph_data_at (current); - - data->generation = max_generation +1 ;pop_commit (&list); -if (data->generation >GENERATION_NUMBER_V1_MAX ) - data->generation =GENERATION_NUMBER_V1_MAX ; +if (max_level >GENERATION_NUMBER_V1_MAX -1 ) + max_level =GENERATION_NUMBER_V1_MAX -1 ; + *topo_level_slab_at (ctx->topo_levels, current) = max_level +1 ; } } }
@@ -2142,6 +2146,7 @@ int write_commit_graph(struct object_directory *odb,int res =0 ;int replace =0 ;struct bloom_filter_settings bloom_settings =DEFAULT_BLOOM_FILTER_SETTINGS ; +struct topo_level_slab topo_levels;if (!commit_graph_compatible (the_repository))return 0 ;
@@ -2163,6 +2168,18 @@ int write_commit_graph(struct object_directory *odb, bloom_settings.max_changed_paths); ctx->bloom_settings = &bloom_settings; +init_topo_level_slab (&topo_levels); + ctx->topo_levels = &topo_levels; + +if (ctx->r->objects->commit_graph) { +struct commit_graph *g = ctx->r->objects->commit_graph; + +while (g) { + g->topo_levels = &topo_levels; + g = g->base_graph; + } + } +if (flags &COMMIT_GRAPH_WRITE_BLOOM_FILTERS ) ctx->changed_paths =1 ;if (!(flags &COMMIT_GRAPH_NO_WRITE_BLOOM_FILTERS )) {
commit-graph.h +1 -0
@@ -73,6 +73,7 @@ struct commit_graph {const unsigned char *chunk_bloom_indexes;const unsigned char *chunk_bloom_data; +struct topo_level_slab *topo_levels;struct bloom_filter_settings *bloom_filter_settings; };
Signature · 1 line
gitgitgadget
Re: [PATCH v4 05/10] commit-graph: add a slab to store topological levels
Quoted 12 lines · Abhishek Kumar via GitGitGadget
From: Abhishek Kumar <[email protected]>
In a later commit we will introduce corrected commit date as the generation number v2. This value will be stored in the new seperate Generation Data chunk. However, to ensure backwards compatibility with "Old" Git we need to continue to write generation number v1, which is topological level, to the commit data chunk. This means that we need to compute both versions of generation numbers when writing the commit-graph file. Therefore, let's introduce a commit-slab to store topological levels; corrected commit date will be stored in the member
generationof structcommit_graph_data.
Quoted 8 lines
When Git creates a split commit-graph, it takes advantage of the generation values that have been computed already and present in existing commit-graph files.
So, let's add a pointer to struct
commit_graphas well as structwrite_commit_graph_contextto the topological level commit-slab and populate it with topological levels while writing a commit-graph file.
I think you meant here "add a pointer in struct commit_graph as well as in struct write_commit_graph_context...".
Perhaps we should add the information that it is done that way to be able to allocate topo_level_slab only when needed, in the write_commit_graph(), and adding new member to those struct is required to pass it through the call chain (modifying struct commit_graph is needed for fill_commit_graph_info()). But that might be too much detail to put in the commit message.
Quoted 7 lines
Signed-off-by Abhishek Kumar <[email protected]>--- commit-graph.c | 47 ++++++++++++++++++++++++++++++++--------------- commit-graph.h | 1 + 2 files changed, 33 insertions(+), 15 deletions(-)
Let me reorder those files for easier review.
commit-graph.h struct commit_graph { · line 78
Quoted 11 lines
commit-graph.h +1
-0@@ -73,6 +73,7 @@ struct commit_graph {const unsigned char *chunk_bloom_indexes;const unsigned char *chunk_bloom_data; +struct topo_level_slab *topo_levels;struct bloom_filter_settings *bloom_filter_settings; };
All right, here we add new member to struct commit_graph type.
commit-graph.c struct write_commit_graph_context { · line 973
Quoted 12 lines
commit-graph.c +1
-0@@ -962,6 +967,7 @@ struct write_commit_graph_context { changed_paths:1 , order_by_pack:1; +struct topo_level_slab *topo_levels;const struct commit_graph_opts *opts;size_t total_bloom_filter_data_size;const struct bloom_filter_settings *bloom_settings;
All right, here we add new member to struct write_commit_graph_context type, which is local to commit-graph.c.
commit-graph.c void git_test_write_commit_graph_or_die(void) · line 68
Quoted 6 lines
commit-graph.c +2
-0@@ -64,6 +64,8 @@ void git_test_write_commit_graph_or_die(void)/* Remember to update object flag allocation in object.h */ #define REACHABLE (1u<<15) +define_commit_slab (topo_level_slab, uint32_t); +
All right, here we define new slab for storing topological levels; this just defines new type. Note that we do not define any setters and getters to handle non-zero initialization, like we have for commit_graph_data_slab.
commit-graph.c static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, · line 775
Quoted 10 lines
commit-graph.c +3
-0/* Keep track of the order in which commits are added to our list. */ define_commit_slab (commit_pos, int);static struct commit_pos commit_pos =COMMIT_SLAB_INIT (1 , commit_pos);@@ -768,6 +770,9 @@ static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, item->date = (timestamp_t)((date_high <<32 ) | date_low); graph_data->generation =get_be32 (commit_data + g->hash_len +8 ) >>2 ; + +if (g->topo_levels) + *topo_level_slab_at (g->topo_levels, item) =get_be32 (commit_data + g->hash_len +8 ) >>2 ;
I guess using get_be32() is repeated in this newly added part of code because previous part would be changed to read in generation number v2, if available, and we won't be then able to use
*topo_level_slab_at(g->topo_levels, item) = graph_data->generation;
All right, that's smart.
I guess that in fill_commit_graph_info() we don't know if we are reading commit-graph, when topo levels slab is not present, or whether we are extending and writing the commit-graph file, when we need to fill it with current commit-graph data.
The fact that fill_commit_graph_info() takes 'struct commit_graph' also explains why we need to add pointer to a topo_levels slab to both structs.
Quoted 3 lines
commit-graph.c +0
-0}static inline void set_commit_tree (struct commit *c,struct tree *t)
[...]
Quoted 17 lines
commit-graph.c +6
-0@@ -2142,6 +2146,7 @@ int write_commit_graph(struct object_directory *odb,int res =0 ;int replace =0 ;struct bloom_filter_settings bloom_settings =DEFAULT_BLOOM_FILTER_SETTINGS ; +struct topo_level_slab topo_levels;if (!commit_graph_compatible (the_repository))return 0 ;@@ -2163,6 +2168,18 @@ int write_commit_graph(struct object_directory *odb, bloom_settings.max_changed_paths); ctx->bloom_settings = &bloom_settings; +init_topo_level_slab (&topo_levels); + ctx->topo_levels = &topo_levels; + +if (ctx->r->objects->commit_graph) { +struct commit_graph *g = ctx->r->objects->commit_graph;
commit-graph.c int write_commit_graph(struct object_directory *odb, · line 2185
Quoted 10 lines
commit-graph.c +7
-0+ +while (g) { + g->topo_levels = &topo_levels; + g = g->base_graph; + } + } +if (flags &COMMIT_GRAPH_WRITE_BLOOM_FILTERS ) ctx->changed_paths =1 ;if (!(flags &COMMIT_GRAPH_NO_WRITE_BLOOM_FILTERS )) {
All right, we need topo_level_slab only for writing the commit-graph, so we allocate it with init_*_slab() in write_commit_graph(), and set pointers to it in struct write_commit_graph_context *ctx and in struct commit_graph for each layer in the commit graph. This is needed to pass it down the call-chain.
Looks good to me.
commit-graph.c static int write_graph_chunk_data(struct hashfile *f, · line 1118
Quoted 7 lines
commit-graph.c +1
-1@@ -1108,7 +1114,7 @@ static int write_graph_chunk_data(struct hashfile *f,else packedDate[0 ] =0 ; - packedDate[0 ] |=htonl (commit_graph_data_at (*list)->generation <<2 ); + packedDate[0 ] |=htonl (*topo_level_slab_at (ctx->topo_levels, *list) <<2 );
All right, write_graph_chunk_data() is called from write_commit_graph(), so we know that cxt->topo_levels is not NULL.
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1360
Quoted 9 lines
commit-graph.c +1
-1packedDate[1 ] =htonl ((*list)->date);hashwrite (f, packedDate,8 );@@ -1350,11 +1356,11 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx)_ ("Computing commit graph generation numbers" ), ctx->commits.nr);for (i =0 ; i < ctx->commits.nr; i++) { -timestamp_t generation =commit_graph_data_at (ctx->commits.list[i])->generation; +timestamp_t level = *topo_level_slab_at (ctx->topo_levels, ctx->commits.list[i]);
All right, we know that compute_generation_numbers() is called by the write_commit_graph(), so we know that cxt->topo_levels is not NULL.
Also, we rename 'generation' to 'level' in preparation for the time when we would be computing *both* topological level (for backward compatibility) and corrected committer date (to be used as generation number v2). All right.
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1364
Quoted 6 lines
commit-graph.c +2
-2display_progress (ctx->progress, i +1 ); -if (generation !=GENERATION_NUMBER_INFINITY && - generation !=GENERATION_NUMBER_ZERO ) +if (level !=GENERATION_NUMBER_INFINITY && + level !=GENERATION_NUMBER_ZERO )continue ;
Same here, the results of renaming of 'generation' local variable to 'level'.
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1371
Quoted 8 lines
commit-graph.c +1
-1commit_list_insert (ctx->commits.list[i], &list);@@ -1362,29 +1368,27 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx)struct commit *current = list->item;struct commit_list *parent;int all_parents_computed =1 ; -uint32_t max_generation =0 ; +uint32_t max_level =0 ;
Similarly, we rename 'max_generation' to 'max_level'.
Quoted 8 lines
commit-graph.c +2
-3for (parent = current->parents; parent; parent = parent->next) { - generation =commit_graph_data_at (parent->item)->generation; + level = *topo_level_slab_at (ctx->topo_levels, parent->item); -if (generation ==GENERATION_NUMBER_INFINITY || - generation ==GENERATION_NUMBER_ZERO ) { + if (level == GENERATION_NUMBER_INFINITY ||
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1384
Quoted 10 lines
commit-graph.c +3
-2+ level == GENERATION_NUMBER_ZERO) { all_parents_computed =0 ;commit_list_insert (parent->item, &list);break ; - }else if (generation > max_generation) { - max_generation = generation; + } else if (level > max_level) { + max_level = level; } }
Continuation of those renames.
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1391
Quoted 12 lines
commit-graph.c +3
-5if (all_parents_computed) { -struct commit_graph_data *data =commit_graph_data_at (current); - - data->generation = max_generation +1 ;pop_commit (&list); -if (data->generation >GENERATION_NUMBER_V1_MAX ) - data->generation =GENERATION_NUMBER_V1_MAX ; +if (max_level >GENERATION_NUMBER_V1_MAX -1 ) + max_level =GENERATION_NUMBER_V1_MAX -1 ; + *topo_level_slab_at (ctx->topo_levels, current) = max_level +1 ;
This is a bit safer way to handle possible overflow: instead of
final = max_found + 1; /* set to maximum plus 1 */
if (final > MAX_POSSIBLE_VALUE) /* handle overflow */
final = MAX_POSSIBLE_VALUE;where we can have problems if MAX_POSSIBLE_VALUE overflows, we use the following pattern:
if (max_found > MAX_POSSIBLE_VALUE - 1) /* handle overflow */
max_found > MAX_POSSIBLE_VALUE - 1;
final = max_found + 1; /* set to maximum plus 1 */
It is just a bit obscured by renaming variable and switch to using commit slab.
It is not that important for topological level, where GENERATION_NUMBER_V1_MAX is smaller than maximum possible value, but it would be important for generation number v2.
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1392
Quoted 3 lines
commit-graph.c +0
-0} } }
Best,
--
Jakub Narębski
PATCH v4 6/10 commit-graph: implement corrected commit date
In reply to Abhishek Kumar via GitGitGadgetFrom: Abhishek Kumar <[email protected]>
With most of preparations done, let's implement corrected commit date.
The corrected commit date for a commit is defined as:
* A commit with no parents (a root commit) has corrected commit date equal to its committer date.
* A commit with at least one parent has corrected commit date equal to the maximum of its commit date and one more than the largest corrected commit date among its parents.
As a special case, a root commit with timestamp of zero (01.01.1970 00:00:00Z) has corrected commit date of one, to be able to distinguish from GENERATION_NUMBER_ZERO (that is, an uncomputed corrected commit date).
To minimize the space required to store corrected commit date, Git stores corrected commit date offsets into the commit-graph file. The corrected commit date offset for a commit is defined as the difference between its corrected commit date and actual commit date.
Storing corrected commit date requires sizeof(timestamp_t) bytes, which in most cases is 64 bits (uintmax_t). However, corrected commit date offsets can be safely stored using only 32-bits. This halves the size of GDAT chunk, which is a reduction of around 6% in the size of commit-graph file.
However, using offsets be problematic if one of commits is malformed but valid and has committerdate of 0 Unix time, as the offset would be the same as corrected commit date and thus require 64-bits to be stored properly.
While Git does not write out offsets at this stage, Git stores the corrected commit dates in member generation of struct commit_graph_data. It will begin writing commit date offsets with the introduction of generation data chunk.
--- commit-graph.c | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-)
commit-graph.c +23 -20
@@ -154,11 +154,6 @@ static int commit_gen_cmp(const void *va, const void *vb)else if (generation_a > generation_b)return 1 ; -/* use date as a heuristic when generations are equal */ -if (a->date < b->date) -return -1 ; -else if (a->date > b->date) -return 1 ;return 0 ; }
@@ -1357,10 +1352,14 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx) ctx->commits.nr);for (i =0 ; i < ctx->commits.nr; i++) {timestamp_t level = *topo_level_slab_at (ctx->topo_levels, ctx->commits.list[i]); +timestamp_t corrected_commit_date =commit_graph_data_at (ctx->commits.list[i])->generation;display_progress (ctx->progress, i +1 );if (level !=GENERATION_NUMBER_INFINITY && - level !=GENERATION_NUMBER_ZERO ) + level !=GENERATION_NUMBER_ZERO && + corrected_commit_date !=GENERATION_NUMBER_INFINITY && + corrected_commit_date !=GENERATION_NUMBER_ZERO + )continue ;commit_list_insert (ctx->commits.list[i], &list);
@@ -1369,17 +1368,25 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx)struct commit_list *parent;int all_parents_computed =1 ;uint32_t max_level =0 ; +timestamp_t max_corrected_commit_date =0 ;for (parent = current->parents; parent; parent = parent->next) { level = *topo_level_slab_at (ctx->topo_levels, parent->item); - + corrected_commit_date =commit_graph_data_at (parent->item)->generation;if (level ==GENERATION_NUMBER_INFINITY || - level ==GENERATION_NUMBER_ZERO ) { + level ==GENERATION_NUMBER_ZERO || + corrected_commit_date ==GENERATION_NUMBER_INFINITY || + corrected_commit_date ==GENERATION_NUMBER_ZERO + ) { all_parents_computed =0 ;commit_list_insert (parent->item, &list);break ; - }else if (level > max_level) { - max_level = level; + }else { +if (level > max_level) + max_level = level; + +if (corrected_commit_date > max_corrected_commit_date) + max_corrected_commit_date = corrected_commit_date; } }
@@ -1389,6 +1396,10 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx)if (max_level >GENERATION_NUMBER_V1_MAX -1 ) max_level =GENERATION_NUMBER_V1_MAX -1 ; *topo_level_slab_at (ctx->topo_levels, current) = max_level +1 ; + +if (current->date && current->date > max_corrected_commit_date) + max_corrected_commit_date = current->date -1 ; +commit_graph_data_at (current)->generation = max_corrected_commit_date +1 ; } } }
@@ -2485,17 +2496,9 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags)if (generation_zero ==GENERATION_ZERO_EXISTS )continue ; -/* -* If one of our parents has generation GENERATION_NUMBER_V1_MAX, then -* our generation is also GENERATION_NUMBER_V1_MAX. Decrement to avoid -* extra logic in the following condition. -*/ -if (max_generation ==GENERATION_NUMBER_V1_MAX ) - max_generation--; - generation =commit_graph_generation (graph_commit); -if (generation != max_generation +1 ) -graph_report (_ ("commit-graph generation for commit %s is %u != %u" ), +if (generation < max_generation +1 ) +graph_report (_ ("commit-graph generation for commit %s is %" PRItime" < %" PRItime),oid_to_hex (&cur_oid), generation, max_generation +1 );
Signature · 1 line
gitgitgadget
Re: [PATCH v4 06/10] commit-graph: implement corrected commit date
Quoted 11 lines · Abhishek Kumar via GitGitGadget
From: Abhishek Kumar <[email protected]>
With most of preparations done, let's implement corrected commit date.
The corrected commit date for a commit is defined as:
* A commit with no parents (a root commit) has corrected commit date equal to its committer date.
* A commit with at least one parent has corrected commit date equal to the maximum of its commit date and one more than the largest corrected commit date among its parents.
All right. We might want to say that it fulfills the same reachability criteria as topological level, but perhaps this level of detail is not necessary here.
Quoted 4 lines
As a special case, a root commit with timestamp of zero (01.01.1970 00:00:00Z) has corrected commit date of one, to be able to distinguish from
GENERATION_NUMBER_ZERO(that is, an uncomputed corrected commit date).
I'm not sure if this special case is really necessary, but it makes for cleaner reasoning.
Quoted 16 lines
To minimize the space required to store corrected commit date, Git stores corrected commit date offsets into the commit-graph file. The corrected commit date offset for a commit is defined as the difference between its corrected commit date and actual commit date.
Storing corrected commit date requires
sizeof(timestamp_t)bytes, which in most cases is 64 bits (uintmax_t). However, corrected commit date offsets can be safely stored using only 32-bits. This halves the size of GDAT chunk, which is a reduction of around 6% in the size of commit-graph file.However, using offsets be problematic if one of commits is malformed but valid and has committerdate of 0 Unix time, as the offset would be the same as corrected commit date and thus require 64-bits to be stored properly.
Quoted 4 lines
While Git does not write out offsets at this stage, Git stores the corrected commit dates in member generation of struct
commit_graph_data. It will begin writing commit date offsets with the introduction of generation data chunk.
All right.
Quoted 2 lines
Signed-off-by Abhishek Kumar <[email protected]>
Somewhere in the commit message we should also describe that this commit changes how commit-graph is verified: from checking that the generation number agrees with _topological level definition_, that is that for a given commit it is 1 more than maximum of its parents (with the caveat that we need to handle GENERATION_NUMBER_V1_MAX values correctly), to checking that slightly weaker condition fulfilled by both topological levels (generation number v1) and by corrected commit date (generation number v2) that for a given commit its generation number is 1 more than maximum of its parents or larger.
But, as far as I understand it, current code does not handle correctly GENERATION_NUMBER_V1_MAX case (if we use generation number v1).
On the other hand we could have simpy use functional check, that generation number used (which can be v1 or v2, or any similar other) fulfills the reachability condition for each edge, which can be simplified to checking that generation(parents) <= generation(commit). If the reachability condition is true for each edge, then it is true for each path, and for each commit.
Quoted 9 lines
--- commit-graph.c | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-)commit-graph.c +0
-0
commit-graph.c static int commit_gen_cmp(const void *va, const void *vb) · line 161
Quoted 8 lines
commit-graph.c +0
-5@@ -154,11 +154,6 @@ static int commit_gen_cmp(const void *va, const void *vb)else if (generation_a > generation_b)return 1 ; -/* use date as a heuristic when generations are equal */ -if (a->date < b->date) -return -1 ; -else if (a->date > b->date) -return 1 ;
Why this change? It is not described in the commit message.
Note that while this tie-breaking fallback doesn't make much sense for corrected committer date generation number v2, this tie-breaking helps if we have to use topological levels (generation number v2).
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1354
Quoted 7 lines
commit-graph.c +0
-0return 0 ; }@@ -1357,10 +1352,14 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx) ctx->commits.nr);for (i =0 ; i < ctx->commits.nr; i++) {timestamp_t level = *topo_level_slab_at (ctx->topo_levels, ctx->commits.list[i]);
Sidenote: I haven't noticed it earlier, but here 'uint32_t' might be enough; no need for 'timestamp_t' for 'level' variable.
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1356
Quoted 2 lines
commit-graph.c +1
-0+timestamp_t corrected_commit_date =commit_graph_data_at (ctx->commits.list[i])->generation;
All right, we compute both generation numbers: topological levels and corrected commit date.
I guess we use 'corrected_commit_date' instead of simply 'generation' to make it asier to remember which is which.
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1361
Quoted 6 lines
commit-graph.c +3
-1display_progress (ctx->progress, i +1 );if (level !=GENERATION_NUMBER_INFINITY && - level !=GENERATION_NUMBER_ZERO ) + level !=GENERATION_NUMBER_ZERO && + corrected_commit_date !=GENERATION_NUMBER_INFINITY && + corrected_commit_date != GENERATION_NUMBER_ZERO
Straightforward addition.
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1362
Quoted 1 line
commit-graph.c +1
-0+ )
Why this closing parenthesis is now in separated line?
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1371
Quoted 8 lines
commit-graph.c +1
-0continue ;commit_list_insert (ctx->commits.list[i], &list);@@ -1369,17 +1368,25 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx)struct commit_list *parent;int all_parents_computed =1 ;uint32_t max_level =0 ; +timestamp_t max_corrected_commit_date =0 ;
All right, straightforward addition.
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1375
Quoted 4 lines
commit-graph.c +0
-1for (parent = current->parents; parent; parent = parent->next) { level = *topo_level_slab_at (ctx->topo_levels, parent->item); -
Why we have removed this empty line?
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1375
Quoted 1 line
commit-graph.c +1
-0+ corrected_commit_date =commit_graph_data_at (parent->item)->generation;
All right.
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1380
Quoted 6 lines
commit-graph.c +4
-1if (level ==GENERATION_NUMBER_INFINITY || - level ==GENERATION_NUMBER_ZERO ) { + level ==GENERATION_NUMBER_ZERO || + corrected_commit_date ==GENERATION_NUMBER_INFINITY || + corrected_commit_date ==GENERATION_NUMBER_ZERO + ) {
All right, same as above.
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1390
Quoted 12 lines
commit-graph.c +6
-2all_parents_computed =0 ;commit_list_insert (parent->item, &list);break ; - }else if (level > max_level) { - max_level = level; + }else { +if (level > max_level) + max_level = level; + +if (corrected_commit_date > max_corrected_commit_date) + max_corrected_commit_date = corrected_commit_date; }
All right, reasonable and straightforward.
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1402
Quoted 10 lines
commit-graph.c +4
-0}@@ -1389,6 +1396,10 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx)if (max_level >GENERATION_NUMBER_V1_MAX -1 ) max_level =GENERATION_NUMBER_V1_MAX -1 ; *topo_level_slab_at (ctx->topo_levels, current) = max_level +1 ; + +if (current->date && current->date > max_corrected_commit_date) + max_corrected_commit_date = current->date -1 ; +commit_graph_data_at (current)->generation = max_corrected_commit_date +1 ;
All right.
Here we use the same trick as in previous commit (and as above) to avoid any possible overflow, to minimize number of conditionals. The fact that max_corrected_commit_date might store incorrect value doesn't matter, as it is reset at beginning of this loop.
Quoted 5 lines
commit-graph.c +0
-0} } }@@ -2485,17 +2496,9 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags)if (generation_zero ==GENERATION_ZERO_EXISTS )
commit-graph.c int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags) · line 2495
Quoted 10 lines
commit-graph.c +0
-8continue ; -/* -* If one of our parents has generation GENERATION_NUMBER_V1_MAX, then -* our generation is also GENERATION_NUMBER_V1_MAX. Decrement to avoid -* extra logic in the following condition. -*/ -if (max_generation ==GENERATION_NUMBER_V1_MAX ) - max_generation--; -
Perhaps in the future we should check that both topological levels, and also corrected committer date (if it exists) for correctness according to their definition. Then the above removed part would be restored (but with s/max_generation/max_level/).
commit-graph.c int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags) · line 2501
Quoted 5 lines
commit-graph.c +2
-2generation =commit_graph_generation (graph_commit); -if (generation != max_generation +1 ) - graph_report(_("commit-graph generation for commit %s is %u != %u"), +if (generation < max_generation +1 ) + graph_report(_("commit-graph generation for commit %s is %"PRItime" < %"PRItime),
All right, so we relaxed the check so that it will be fulfilled by generation number v2 (and also by generation number v1, as it implies the more strict check for v1).
What would happen however if generation holds topological levels, and it is GENERATION_NUMBER_V1_MAX for at least one parent, which means it is GENERATION_NUMBER_V1_MAX for a commit? As you can check, the condition would be true: GENERATION_NUMBER_V1_MAX < GENERATION_NUMBER_V1_MAX + 1, so the git commit-graph verify would incorrectly say that there is a problem with generation number, while there isn't one (false positive detection of error).
Sidenote: I think we don't have to worry about having to introduce GENERATION_NUMBER_V2_MAX, as the in-memory size (of reconstructed from disck representation) corrected commiter date is the same as of commiter date itself, plus some, and I don't see us coming close to 64-bit limit of timestamp_t for commit dates.
commit-graph.c int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags) · line 2504
Quoted 3 lines
commit-graph.c +0
-0oid_to_hex (&cur_oid), generation, max_generation + 1);
Best,
Signature · 1 line
Jakub Narębski
Re: [PATCH v4 06/10] commit-graph: implement corrected commit date
Quoted 6 lines · Jakub Narębski
Quoted 3 lines · Abhishek Kumar via GitGitGadget
From: Abhishek Kumar <[email protected]>
...Signed-off-by Abhishek Kumar <[email protected]>
Quoted 9 lines
Somewhere in the commit message we should also describe that this commit changes how commit-graph is verified: from checking that the generation number agrees with
_topologicalleveldefinition_, that is that for a given commit it is 1 more than maximum of its parents (with the caveat that we need to handleGENERATION_NUMBER_V1_MAXvalues correctly), to checking that slightly weaker condition fulfilled by both topological levels (generation number v1) and by corrected commit date (generation number v2) that for a given commit its generation number is 1 more than maximum of its parents or larger.
Sure, that makes sense. Will add.
Quoted 31 lines
But, as far as I understand it, current code does not handle correctly
GENERATION_NUMBER_V1_MAXcase (if we use generation number v1).On the other hand we could have simpy use functional check, that generation number used (which can be v1 or v2, or any similar other) fulfills the reachability condition for each edge, which can be simplified to checking that generation(parents) <= generation(commit). If the reachability condition is true for each edge, then it is true for each path, and for each commit.
Quoted 9 lines
--- commit-graph.c | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-)commit-graph.c +0
-0commit-graph.c static int commit_gen_cmp(const void *va, const void *vb) · line 161
Quoted 8 lines
commit-graph.c +0
-5@@ -154,11 +154,6 @@ static int commit_gen_cmp(const void *va, const void *vb)else if (generation_a > generation_b)return 1 ; -/* use date as a heuristic when generations are equal */ -if (a->date < b->date) -return -1 ; -else if (a->date > b->date) -return 1 ;Why this change? It is not described in the commit message.
Quoted 4 lines
Note that while this tie-breaking fallback doesn't make much sense for corrected committer date generation number v2, this tie-breaking helps if we have to use topological levels (generation number v2).
Right, I should have mentioned this change (and it's not something that makes a difference either way).
We call commit_gen_cmp() only when we are sorting commits by generation to speed up computation of Bloom filters i.e. while writing a commit graph (either split commit-graph or a simple commit-graph).
Since we are always computing and storing corrected commit date when we are writing (whether we write a GDAT chunk or not), using date as heuristic is longer required.
Quoted 11 lines
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1354
Quoted 7 lines
commit-graph.c +0
-0return 0 ; }@@ -1357,10 +1352,14 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx) ctx->commits.nr);for (i =0 ; i < ctx->commits.nr; i++) {timestamp_t level = *topo_level_slab_at (ctx->topo_levels, ctx->commits.list[i]);Sidenote: I haven't noticed it earlier, but here '
uint32_t' might be enough; no need for 'timestamp_t' for 'level' variable.
Quoted 2 lines
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1356
Quoted 2 lines
commit-graph.c +1
-0+timestamp_t corrected_commit_date =commit_graph_data_at (ctx->commits.list[i])->generation;
We need the 'timestamp_t' as we are comparing level with the now 64-bits GENERATION_NUMBER_INFINITY. I thought uint32_t would be promoted to timestamp_t. I have a hunch that since we are explicitly using a fixed width data type, compiler is unwilling to type coerce into broader data types.
Advice on this appreciated.
Quoted 115 lines
All right, we compute both generation numbers: topological levels and corrected commit date.
I guess we use '
corrected_commit_date' instead of simply 'generation' to make it asier to remember which is which.commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1361
Quoted 6 lines
commit-graph.c +3
-1display_progress (ctx->progress, i +1 );if (level !=GENERATION_NUMBER_INFINITY && - level !=GENERATION_NUMBER_ZERO ) + level !=GENERATION_NUMBER_ZERO && + corrected_commit_date !=GENERATION_NUMBER_INFINITY && + corrected_commit_date != GENERATION_NUMBER_ZEROStraightforward addition.
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1362
Quoted 1 line
commit-graph.c +1
-0+ )Why this closing parenthesis is now in separated line?
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1371
Quoted 8 lines
commit-graph.c +1
-0continue ;commit_list_insert (ctx->commits.list[i], &list);@@ -1369,17 +1368,25 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx)struct commit_list *parent;int all_parents_computed =1 ;uint32_t max_level =0 ; +timestamp_t max_corrected_commit_date =0 ;All right, straightforward addition.
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1375
Quoted 4 lines
commit-graph.c +0
-1for (parent = current->parents; parent; parent = parent->next) { level = *topo_level_slab_at (ctx->topo_levels, parent->item); -Why we have removed this empty line?
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1375
Quoted 1 line
commit-graph.c +1
-0+ corrected_commit_date =commit_graph_data_at (parent->item)->generation;All right.
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1380
Quoted 6 lines
commit-graph.c +4
-1if (level ==GENERATION_NUMBER_INFINITY || - level ==GENERATION_NUMBER_ZERO ) { + level ==GENERATION_NUMBER_ZERO || + corrected_commit_date ==GENERATION_NUMBER_INFINITY || + corrected_commit_date ==GENERATION_NUMBER_ZERO + ) {All right, same as above.
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1390
Quoted 12 lines
commit-graph.c +6
-2all_parents_computed =0 ;commit_list_insert (parent->item, &list);break ; - }else if (level > max_level) { - max_level = level; + }else { +if (level > max_level) + max_level = level; + +if (corrected_commit_date > max_corrected_commit_date) + max_corrected_commit_date = corrected_commit_date; }All right, reasonable and straightforward.
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1402
Quoted 10 lines
commit-graph.c +4
-0}@@ -1389,6 +1396,10 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx)if (max_level >GENERATION_NUMBER_V1_MAX -1 ) max_level =GENERATION_NUMBER_V1_MAX -1 ; *topo_level_slab_at (ctx->topo_levels, current) = max_level +1 ; + +if (current->date && current->date > max_corrected_commit_date) + max_corrected_commit_date = current->date -1 ; +commit_graph_data_at (current)->generation = max_corrected_commit_date +1 ;All right.
Here we use the same trick as in previous commit (and as above) to avoid any possible overflow, to minimize number of conditionals. The fact that
max_corrected_commit_datemight store incorrect value doesn't matter, as it is reset at beginning of this loop.Quoted 5 lines
commit-graph.c +0
-0} } }@@ -2485,17 +2496,9 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags)if (generation_zero ==GENERATION_ZERO_EXISTS )commit-graph.c int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags) · line 2495
Quoted 10 lines
commit-graph.c +0
-8continue ; -/* -* If one of our parents has generation GENERATION_NUMBER_V1_MAX, then -* our generation is also GENERATION_NUMBER_V1_MAX. Decrement to avoid -* extra logic in the following condition. -*/ -if (max_generation ==GENERATION_NUMBER_V1_MAX ) - max_generation--; -Perhaps in the future we should check that both topological levels, and also corrected committer date (if it exists) for correctness according to their definition. Then the above removed part would be restored (but with s/max_generation/max_level/).
commit-graph.c int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags) · line 2501
Quoted 5 lines
commit-graph.c +2
-2generation =commit_graph_generation (graph_commit); -if (generation != max_generation +1 ) - graph_report(_("commit-graph generation for commit %s is %u != %u"), +if (generation < max_generation +1 ) + graph_report(_("commit-graph generation for commit %s is %"PRItime" < %"PRItime),All right, so we relaxed the check so that it will be fulfilled by generation number v2 (and also by generation number v1, as it implies the more strict check for v1).
Quoted 7 lines
What would happen however if generation holds topological levels, and it is
GENERATION_NUMBER_V1_MAXfor at least one parent, which means it isGENERATION_NUMBER_V1_MAXfor a commit? As you can check, the condition would be true:GENERATION_NUMBER_V1_MAX<GENERATION_NUMBER_V1_MAX+ 1, so thegit commit-graph verifywould incorrectly say that there is a problem with generation number, while there isn't one (false positive detection of error).
Alright, so the above block still makes sense if we are working with topological levels but not with corrected commit dates. Instead of removing it, I will modify the condition to check that one of our parents has GENERATION_NUMBER_V1_MAX and the graph uses topological levels.
Suprised that no test breaks by this change.
I have also moved changes in the verify function to the next patch, as we cannot write or read corrected commit dates yet - so little sense in modifying verify.
Quoted 7 lines
Sidenote: I think we don't have to worry about having to introduce
GENERATION_NUMBER_V2_MAX, as the in-memory size (of reconstructed from disck representation) corrected commiter date is the same as of commiter date itself, plus some, and I don't see us coming close to 64-bit limit oftimestamp_tfor commit dates.
Quoted 7 lines
commit-graph.c int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags) · line 2504
Quoted 3 lines
commit-graph.c +0
-0oid_to_hex (&cur_oid), generation, max_generation + 1);Best,
Signature · 1 line
Jakub Narębski
Thanks
- Abhishek
Re: [PATCH v4 06/10] commit-graph: implement corrected commit date
Hello Abhishek,
Quoted 17 lines · Abhishek Kumar
Quoted 6 lines · Jakub Narębski
Quoted 3 lines · Abhishek Kumar via GitGitGadget
From: Abhishek Kumar <[email protected]>
...Signed-off-by Abhishek Kumar <[email protected]>Quoted 9 lines
Somewhere in the commit message we should also describe that this commit changes how commit-graph is verified: from checking that the generation number agrees with
_topologicalleveldefinition_, that is that for a given commit it is 1 more than maximum of its parents (with the caveat that we need to handleGENERATION_NUMBER_V1_MAXvalues correctly), to checking that slightly weaker condition fulfilled by both topological levels (generation number v1) and by corrected commit date (generation number v2) that for a given commit its generation number is 1 more than maximum of its parents or larger.
Quoted 1 line
Sure, that makes sense. Will add.
Actually this description should match whatever we decide about mechanism for verifying correctness of generation numbers (see below). Because we have to choose one.
Quoted 10 lines
Quoted 10 lines
But, as far as I understand it, current code does not handle correctly
GENERATION_NUMBER_V1_MAXcase (if we use generation number v1).On the other hand we could have simpy use functional check, that generation number used (which can be v1 or v2, or any similar other) fulfills the reachability condition for each edge, which can be simplified to checking that generation(parents) <= generation(commit). If the reachability condition is true for each edge, then it is true for each path, and for each commit.
See below.
Quoted 28 lines
Quoted 20 lines
Quoted 9 lines
--- commit-graph.c | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-)commit-graph.c +0
-0commit-graph.c static int commit_gen_cmp(const void *va, const void *vb) · line 161
Quoted 8 lines
commit-graph.c +0
-5@@ -154,11 +154,6 @@ static int commit_gen_cmp(const void *va, const void *vb)else if (generation_a > generation_b)return 1 ; -/* use date as a heuristic when generations are equal */ -if (a->date < b->date) -return -1 ; -else if (a->date > b->date) -return 1 ;Why this change? It is not described in the commit message.
Quoted 4 lines
Note that while this tie-breaking fallback doesn't make much sense for corrected committer date generation number v2, this tie-breaking helps if we have to use topological levels (generation number v2).
Right, I should have mentioned this change (and it's not something that makes a difference either way).
Quoted 7 lines
We call
commit_gen_cmp()only when we are sorting commits by generation to speed up computation of Bloom filters i.e. while writing a commit graph (either split commit-graph or a simple commit-graph).Since we are always computing and storing corrected commit date when we are writing (whether we write a GDAT chunk or not), using date as heuristic is longer required.
Thanks. This description really should be added to the commit message, because (yet again?) I was confused by this change.
Sidenote: it is not obvious at least to me that this function is used only for sorting commits to speed up computation of Bloom filters while writing the commit-graph (git commit-graph write --changed-paths [other options]).
Quoted 20 lines
Quoted 11 lines
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1354
Quoted 7 lines
commit-graph.c +0
-0return 0 ; }@@ -1357,10 +1352,14 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx) ctx->commits.nr);for (i =0 ; i < ctx->commits.nr; i++) {timestamp_t level = *topo_level_slab_at (ctx->topo_levels, ctx->commits.list[i]);Sidenote: I haven't noticed it earlier, but here '
uint32_t' might be enough; no need for 'timestamp_t' for 'level' variable.Quoted 2 lines
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1356
Quoted 2 lines
commit-graph.c +1
-0+timestamp_t corrected_commit_date =commit_graph_data_at (ctx->commits.list[i])->generation;We need the '
timestamp_t' as we are comparing level with the now 64-bitsGENERATION_NUMBER_INFINITY. I thoughtuint32_twould be promoted totimestamp_t. I have a hunch that since we are explicitly using a fixed width data type, compiler is unwilling to type coerce into broader data types.
Quoted 1 line
Advice on this appreciated.
All right, so the wider type is used because of comparison with wide-uint GENERATION_NUMBER_INFINITY. I stand corrected.
[...]
Quoted 36 lines
Quoted 28 lines
commit-graph.c int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags) · line 2495
Quoted 12 lines
commit-graph.c +0
-8@@ -2485,17 +2496,9 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags)if (generation_zero ==GENERATION_ZERO_EXISTS )continue ; -/* -* If one of our parents has generation GENERATION_NUMBER_V1_MAX, then -* our generation is also GENERATION_NUMBER_V1_MAX. Decrement to avoid -* extra logic in the following condition. -*/ -if (max_generation ==GENERATION_NUMBER_V1_MAX ) - max_generation--; -Perhaps in the future we should check that both topological levels, and also corrected committer date (if it exists) for correctness according to their definition. Then the above removed part would be restored (but with s/max_generation/max_level/).
commit-graph.c int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags) · line 2501
Quoted 5 lines
commit-graph.c +2
-2generation =commit_graph_generation (graph_commit); -if (generation != max_generation +1 ) - graph_report(_("commit-graph generation for commit %s is %u != %u"), +if (generation < max_generation +1 ) + graph_report(_("commit-graph generation for commit %s is %"PRItime" < %"PRItime),All right, so we relaxed the check so that it will be fulfilled by generation number v2 (and also by generation number v1, as it implies the more strict check for v1).
Quoted 7 lines
What would happen however if generation holds topological levels, and it is
GENERATION_NUMBER_V1_MAXfor at least one parent, which means it isGENERATION_NUMBER_V1_MAXfor a commit? As you can check, the condition would be true:GENERATION_NUMBER_V1_MAX<GENERATION_NUMBER_V1_MAX+ 1, so thegit commit-graph verifywould incorrectly say that there is a problem with generation number, while there isn't one (false positive detection of error).
Quoted 4 lines
Alright, so the above block still makes sense if we are working with topological levels but not with corrected commit dates. Instead of removing it, I will modify the condition to check that one of our parents has
GENERATION_NUMBER_V1_MAXand the graph uses topological levels.
That is one of the 3 possible solutions I can think of.
I. First solution is to switch from checking that generation number matches its definition to checking that the [weaker] reachability condition for the generation number is true, that is:
if (generation < max_generation)
graph_report(_("commit-graph generation for commit %s is %"PRItime" < %"PRItime),
The [weaker] reachability condition for generation numbers states that
A reachable from B => gen(A) <= gen(B)
This condition is true even if one or more generation numbers is GENERATION_NUMBER_ZERO (uninitialized or written by old git version), GENERATION_NUMBER_V1_MAX (we hit storage limitations, can happen only for generation number v1), or GENERATION_NUMBER_INFINITY (for commits outside of the serialized commit-graph, doesn't matter and cannot happen during verification of the commit-graph data by definition).
This means that if P* is the parent of C with the maximal generation number, and gen(C) < gen(P*) is true (while gen(P*) <= gen(C) should be true), then there is a problem with generation number.
This is why I thought you were going for, and what I have proposed.
Advantages:
- we are testing what actually matters for speeding up reachability queries, namely that the reachability property holds true
- the test works for generation number v1, generation number v2, and any possible future use-compatibile generation number (not that I think we would need any)
- least complicated solution
Disadvantages:
- weaker test that we have had for generation number v1 (topological levels), and weaker that possible test for generation number v2 that we could have (see below)
II. Verify corrected committed date (generation number v2) if available, and verify topological levels (generation number v1) otherwise, checking that it matches the definition of it -- using version-specific checks.
This would probably mean adding a conditional around the code verifying that given generation number is correct, possibly:
if (g->read_generation_data) {
/* verify corrected commit date */
} else {
/* current code for verifying topological levels */
}II.a. For topological levels (generation number v1) we would continue checking that it matches the definition, that is that the following condition holds:
gen(C) = max_{P: P ∈ parents(C)} gen(P) + 1
This includes code for handling the case where max_generation, holding max_{P: P ∈ parents(C)} gen(P), is GENERATION_NUMBER_V1_MAX.
II.b. For corrected commiter dates (generation number v2) we can use the code proposed by this revision of this commit, namely we check if the following condition holds:
gen(P) + 1 <= gen(C) for each P \in parents(C)
or, in other words:
max_{P: P ∈ parents(C)} { gen(P) } + 1 <= gen(C)
Which could be checked using the following code (i.e. current state after this revision of this patch):
if (generation < max_generation + 1)
graph_report(_("commit-graph generation for commit %s is %"PRItime" < %"PRItime),
This is what I think you are proposing now.
Additionally, theoretically we could also check that the following condition holds for corrected commiter date:
committer_date(C) <= gen_v2(C)
but this is automatically fufilled because we use non-negative offsets to store corrected committed date info.
Alternatively we can check for compliance with the definition of the corrected committer date:
if (max_generation + 1 <= graph_commit->date) {
/* commit date does not need correction */
if (generation != graph_commit->date) graph_report(_("commit-graph corrected commit date for commit %s "
"is %"PRItime" != %"PRItime" commit date"),
...);
} else {
if (generation != max_generation + 1)
graph_report(_("commit-graph generation v2 for commit %s is %"PRItime" != %"PRItime),
...);}
Though I think it might be overkill.
Advantages:
- more strict tests, checking generation numbers (v2 if present, v1 otherwise) against their definition
- if there is no GDAT chunk, verify works just like it did before
Disadvantages:
- more complicated code
- possibly measurable performance degradation due to extra conditional
III. Like II., but if there is generation numbers chunk (GDAT chunk), we verify *both* topological levels (v1) and corrected commit date (v2) against their definition. If GDAT chunk is not present, it reduces to current code (before this patch series).
Advantages:
- if there is no GDAT chunk, verify works just like it did before
- most strict tests, verifying all the data: both generation number v1 and generation number v2 -- if possible
Disadvantages:
- most complex code; we need to somehow extract topological levels if the GDAT chunk is present (they are not on graph data slab in this case); I have not even started to think how it could be done
- slower verification
Quoted 1 line
Suprised that no test breaks by this change.
I don't whink we have any test that created commit graph with topological levels greater than GENERATION_NUMBER_V1_MAX; this would be expensive and have to be of course protected by GIT_TEST_LONG aka EXPENSIVE prerequisite.
# GIT_TEST_COMMIT_GRAPH_NO_GDAT=1 is here to force verification of topological levels test_expect_success EXPENSIVE 'verify handles topological levels > GENERATION_NUMBER_V1_MAX' '
rm -rf long_chain &&
git init long_chain &&
test_commit_bulk -C long_chain 1073741824 &&
(
cd long_chain &&
GIT_TEST_COMMIT_GRAPH_NO_GDAT=1 git commit-graph write &&
git commit-graph verify
)'
This however lies slightly outside the scope of this patch series, though if you could add this test (in a separate patch), after testing it, it would be very nice.
Quoted 4 lines
I have also moved changes in the verify function to the next patch, as we cannot write or read corrected commit dates yet - so little sense in modifying verify.
I think putting changes to the verify function in a separate patch, be it before or after this one (depending on the choice of the algorithm for verification, see above) would be a good idea.
Quoted 10 lines
Quoted 10 lines
Sidenote: I think we don't have to worry about having to introduce
GENERATION_NUMBER_V2_MAX, as the in-memory size (of reconstructed from disck representation) corrected commiter date is the same as of commiter date itself, plus some, and I don't see us coming close to 64-bit limit oftimestamp_tfor commit dates.Quoted 3 lines
commit-graph.c +0
-0oid_to_hex (&cur_oid), generation, max_generation + 1);
Best,
Signature · 1 line
Jakub Narębski
Re: [PATCH v4 06/10] commit-graph: implement corrected commit date
Hi Abhishek,
Quoted 18 lines · Jakub Narębski
Hello Abhishek,
Quoted 15 lines · Abhishek Kumar
Quoted 14 lines · Jakub Narębski
Quoted 3 lines · Abhishek Kumar via GitGitGadget
From: Abhishek Kumar <[email protected]>
...Signed-off-by Abhishek Kumar <[email protected]>Somewhere in the commit message we should also describe that this commit changes how commit-graph is verified: from checking that the generation number agrees with
_topologicalleveldefinition_, that is that for a given commit it is 1 more than maximum of its parents (with the caveat that we need to handleGENERATION_NUMBER_V1_MAXvalues correctly), to checking that slightly weaker condition fulfilled by both topological levels (generation number v1) and by corrected commit date (generation number v2) that for a given commit its generation number is 1 more than maximum of its parents or larger.
Quoted 4 lines
Quoted 1 line
Sure, that makes sense. Will add.
Actually this description should match whatever we decide about mechanism for verifying correctness of generation numbers (see below). Because we have to choose one.
This may be not part of the the main project, but could you consider, if time permits, also adding some entries into the Git Glossary (git help glossary) for the various terms we are using here and elsewhere, e.g. 'topological levels', 'generation number', 'corrected commit date' (and its fancy technical name for the use of date heuristics e.g. the 'chronological ordering';).
The glossary can provide a reference, once the issues are resolved. The History Simplification and Commit Ordering section of git-log maybe a useful guide to some of the terms that would link to the glossary. --
Philip
Quoted 283 lines
Quoted 9 lines
Quoted 9 lines
But, as far as I understand it, current code does not handle correctly
GENERATION_NUMBER_V1_MAXcase (if we use generation number v1).On the other hand we could have simpy use functional check, that generation number used (which can be v1 or v2, or any similar other) fulfills the reachability condition for each edge, which can be simplified to checking that generation(parents) <= generation(commit). If the reachability condition is true for each edge, then it is true for each path, and for each commit.
See below.
Quoted 26 lines
Quoted 19 lines
Quoted 9 lines
--- commit-graph.c | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-)commit-graph.c +0
-0commit-graph.c static int commit_gen_cmp(const void *va, const void *vb) · line 161
Quoted 8 lines
commit-graph.c +0
-5@@ -154,11 +154,6 @@ static int commit_gen_cmp(const void *va, const void *vb)else if (generation_a > generation_b)return 1 ; -/* use date as a heuristic when generations are equal */ -if (a->date < b->date) -return -1 ; -else if (a->date > b->date) -return 1 ;Why this change? It is not described in the commit message.
Quoted 4 lines
Note that while this tie-breaking fallback doesn't make much sense for corrected committer date generation number v2, this tie-breaking helps if we have to use topological levels (generation number v2).
Right, I should have mentioned this change (and it's not something that makes a difference either way).
Quoted 7 lines
We call
commit_gen_cmp()only when we are sorting commits by generation to speed up computation of Bloom filters i.e. while writing a commit graph (either split commit-graph or a simple commit-graph).Since we are always computing and storing corrected commit date when we are writing (whether we write a GDAT chunk or not), using date as heuristic is longer required.
Thanks. This description really should be added to the commit message, because (yet again?) I was confused by this change.
Sidenote: it is not obvious at least to me that this function is used only for sorting commits to speed up computation of Bloom filters while writing the commit-graph (
git commit-graph write --changed-paths [other options]).Quoted 18 lines
Quoted 10 lines
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1354
Quoted 7 lines
commit-graph.c +0
-0return 0 ; }@@ -1357,10 +1352,14 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx) ctx->commits.nr);for (i =0 ; i < ctx->commits.nr; i++) {timestamp_t level = *topo_level_slab_at (ctx->topo_levels, ctx->commits.list[i]);Sidenote: I haven't noticed it earlier, but here '
uint32_t' might be enough; no need for 'timestamp_t' for 'level' variable.Quoted 2 lines
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1356
Quoted 2 lines
commit-graph.c +1
-0+timestamp_t corrected_commit_date =commit_graph_data_at (ctx->commits.list[i])->generation;We need the '
timestamp_t' as we are comparing level with the now 64-bitsGENERATION_NUMBER_INFINITY. I thoughtuint32_twould be promoted totimestamp_t. I have a hunch that since we are explicitly using a fixed width data type, compiler is unwilling to type coerce into broader data types.Quoted 1 line
Advice on this appreciated.
All right, so the wider type is used because of comparison with wide-uint
GENERATION_NUMBER_INFINITY. I stand corrected.[...]
Quoted 33 lines
Quoted 26 lines
commit-graph.c int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags) · line 2495
Quoted 12 lines
commit-graph.c +0
-8@@ -2485,17 +2496,9 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags)if (generation_zero ==GENERATION_ZERO_EXISTS )continue ; -/* -* If one of our parents has generation GENERATION_NUMBER_V1_MAX, then -* our generation is also GENERATION_NUMBER_V1_MAX. Decrement to avoid -* extra logic in the following condition. -*/ -if (max_generation ==GENERATION_NUMBER_V1_MAX ) - max_generation--; -Perhaps in the future we should check that both topological levels, and also corrected committer date (if it exists) for correctness according to their definition. Then the above removed part would be restored (but with s/max_generation/max_level/).
commit-graph.c int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags) · line 2501
Quoted 5 lines
commit-graph.c +2
-2generation =commit_graph_generation (graph_commit); -if (generation != max_generation +1 ) - graph_report(_("commit-graph generation for commit %s is %u != %u"), +if (generation < max_generation +1 ) + graph_report(_("commit-graph generation for commit %s is %"PRItime" < %"PRItime),All right, so we relaxed the check so that it will be fulfilled by generation number v2 (and also by generation number v1, as it implies the more strict check for v1).
Quoted 7 lines
What would happen however if generation holds topological levels, and it is
GENERATION_NUMBER_V1_MAXfor at least one parent, which means it isGENERATION_NUMBER_V1_MAXfor a commit? As you can check, the condition would be true:GENERATION_NUMBER_V1_MAX<GENERATION_NUMBER_V1_MAX+ 1, so thegit commit-graph verifywould incorrectly say that there is a problem with generation number, while there isn't one (false positive detection of error).Quoted 4 lines
Alright, so the above block still makes sense if we are working with topological levels but not with corrected commit dates. Instead of removing it, I will modify the condition to check that one of our parents has
GENERATION_NUMBER_V1_MAXand the graph uses topological levels.That is one of the 3 possible solutions I can think of.
I. First solution is to switch from checking that generation number matches its definition to checking that the [weaker] reachability condition for the generation number is true, that is:
(quoted diff) +0
-0if (generation < max_generation) graph_report(_("commit-graph generation for commit %s is %"PRItime" < %"PRItime),The [weaker] reachability condition for generation numbers states that
A reachable from B => gen(A) <= gen(B)
This condition is true even if one or more generation numbers is
GENERATION_NUMBER_ZERO(uninitialized or written by old git version),GENERATION_NUMBER_V1_MAX(we hit storage limitations, can happen only for generation number v1), orGENERATION_NUMBER_INFINITY(for commits outside of the serialized commit-graph, doesn't matter and cannot happen during verification of the commit-graph data by definition).This means that if P* is the parent of C with the maximal generation number, and gen(C) <
gen(P*)is true (whilegen(P*)<= gen(C) should be true), then there is a problem with generation number.This is why I thought you were going for, and what I have proposed.
Advantages:
- we are testing what actually matters for speeding up reachability queries, namely that the reachability property holds true
- the test works for generation number v1, generation number v2, and any possible future use-compatibile generation number (not that I think we would need any)
- least complicated solutionDisadvantages:
- weaker test that we have had for generation number v1 (topological levels), and weaker that possible test for generation number v2 that we could have (see below)II. Verify corrected committed date (generation number v2) if available, and verify topological levels (generation number v1) otherwise, checking that it matches the definition of it -- using version-specific checks.
This would probably mean adding a conditional around the code verifying that given generation number is correct, possibly:
if (g->read_generation_data) { /* verify corrected commit date */ } else { /* current code for verifying topological levels */ }II.a. For topological levels (generation number v1) we would continue checking that it matches the definition, that is that the following condition holds:
gen(C) = max_{P: P ∈ parents(C)} gen(P) + 1
This includes code for handling the case where
max_generation, holding max_{P: P ∈ parents(C)} gen(P), isGENERATION_NUMBER_V1_MAX.II.b. For corrected commiter dates (generation number v2) we can use the code proposed by this revision of this commit, namely we check if the following condition holds:
gen(P) + 1 <= gen(C) for each P \in parents(C)
or, in other words:
max_{P: P ∈ parents(C)} { gen(P) } + 1 <= gen(C)
Which could be checked using the following code (i.e. current state after this revision of this patch):
if (generation <
max_generation+ 1)
graph_report(_("commit-graph generation for commit %s is %"PRItime" < %"PRItime),This is what I think you are proposing now.
Additionally, theoretically we could also check that the following condition holds for corrected commiter date:
committer_date(C) <= gen_v2(C)but this is automatically fufilled because we use non-negative offsets to store corrected committed date info.
Alternatively we can check for compliance with the definition of the corrected committer date:
if (
max_generation+ 1 <=graph_commit->date) {
/* commit date does not need correction */
if (generation !=graph_commit->date) graph_report(_("commit-graph corrected commit date for commit %s "
"is %"PRItime" != %"PRItime" commit date"),...); } else { if (generation != max_generation + 1) graph_report(_("commit-graph generation v2 for commit %s is %"PRItime" != %"PRItime), ...);}
Though I think it might be overkill.
Advantages:
- more strict tests, checking generation numbers (v2 if present, v1 otherwise) against their definition
- if there is no GDAT chunk, verify works just like it did beforeDisadvantages:
- more complicated code
- possibly measurable performance degradation due to extra conditionalIII. Like II., but if there is generation numbers chunk (GDAT chunk), we verify *both* topological levels (v1) and corrected commit date (v2) against their definition. If GDAT chunk is not present, it reduces to current code (before this patch series).
Advantages:
- if there is no GDAT chunk, verify works just like it did before
- most strict tests, verifying all the data: both generation number v1 and generation number v2 -- if possibleDisadvantages:
- most complex code; we need to somehow extract topological levels if the GDAT chunk is present (they are not on graph data slab in this case); I have not even started to think how it could be done
- slower verificationQuoted 1 line
Suprised that no test breaks by this change.
I don't whink we have any test that created commit graph with topological levels greater than
GENERATION_NUMBER_V1_MAX; this would be expensive and have to be of course protected byGIT_TEST_LONGaka EXPENSIVE prerequisite.# GIT_TEST_COMMIT_GRAPH_NO_GDAT=1 is here to force verification of topological levels
test_expect_successEXPENSIVE 'verify handles topological levels >GENERATION_NUMBER_V1_MAX' '
rm -rflong_chain&&
git initlong_chain&&
test_commit_bulk-Clong_chain1073741824 &&( cd long_chain && GIT_TEST_COMMIT_GRAPH_NO_GDAT=1 git commit-graph write && git commit-graph verify )'
This however lies slightly outside the scope of this patch series, though if you could add this test (in a separate patch), after testing it, it would be very nice.
Quoted 3 lines
I have also moved changes in the verify function to the next patch, as we cannot write or read corrected commit dates yet - so little sense in modifying verify.
I think putting changes to the verify function in a separate patch, be it before or after this one (depending on the choice of the algorithm for verification, see above) would be a good idea.
Quoted 9 lines
Quoted 9 lines
Sidenote: I think we don't have to worry about having to introduce
GENERATION_NUMBER_V2_MAX, as the in-memory size (of reconstructed from disck representation) corrected commiter date is the same as of commiter date itself, plus some, and I don't see us coming close to 64-bit limit oftimestamp_tfor commit dates.Quoted 3 lines
commit-graph.c +0
-0oid_to_hex (&cur_oid), generation, max_generation + 1);Best,
Re: [PATCH v4 06/10] commit-graph: implement corrected commit date
Quoted 10 lines · Philip Oakley
This may be not part of the the main project, but could you consider, if time permits, also adding some entries into the Git Glossary (
git help glossary) for the various terms we are using here and elsewhere, e.g. 'topological levels', 'generation number', 'corrected commit date' (and its fancy technical name for the use of date heuristics e.g. the 'chronological ordering';).The glossary can provide a reference, once the issues are resolved. The History Simplification and Commit Ordering section of git-log maybe a useful guide to some of the terms that would link to the glossary.
Ah, I first thought that Documentation/rev-list-options.txt (which is the relevant part of "git log" documentation you mention here) already have references to deep technical terms explained in the glossary and you are suggesting Abhishek to mimic the arrangement by adding new and agreed-upon terms to the glossary and referring to them from the commit-graph documentation updated by this series.
But sadly that is not the case. What you are saying is that you noticed that rev-list-options.txt needs a similar "the terms we use to explain these two sections should be defined and explained in the glossary (if they are not) and new references to glossary should be added there" update.
In any case, that is a very good suggestion. I agree that updating "git log" doc may be outside the scope of Abhishek's theme, but it would be very good to have such an update by anybody ;-)
Thanks
Extending and updating gitglossary (was: Re: [PATCH v4 06/10] commit-graph: implement corrected commit date)
Quoted 26 lines · Junio C Hamano
Quoted 10 lines · Philip Oakley
This may be not part of the the main project, but could you consider, if time permits, also adding some entries into the Git Glossary (
git help glossary) for the various terms we are using here and elsewhere, e.g. 'topological levels', 'generation number', 'corrected commit date' (and its fancy technical name for the use of date heuristics e.g. the 'chronological ordering';).The glossary can provide a reference, once the issues are resolved. The History Simplification and Commit Ordering section of git-log maybe a useful guide to some of the terms that would link to the glossary.
Ah, I first thought that
Documentation/rev-list-options.txt(which is the relevant part of "git log" documentation you mention here) already have references to deep technical terms explained in the glossary and you are suggesting Abhishek to mimic the arrangement by adding new and agreed-upon terms to the glossary and referring to them from the commit-graph documentation updated by this series.But sadly that is not the case. What you are saying is that you noticed that rev-list-options.txt needs a similar "the terms we use to explain these two sections should be defined and explained in the glossary (if they are not) and new references to glossary should be added there" update.
Quoted 3 lines
In any case, that is a very good suggestion. I agree that updating "git log" doc may be outside the scope of Abhishek's theme, but it would be very good to have such an update by anybody ;-)
The only possible problem I see with this suggestion is that some of those terms (like 'topological levels' and 'corrected commit date') are technical terms that should be not of concern for Git user, only for developers working on Git. (However one could encounter the term "generation number" in git commit-graph verify output.)
I don't think adding technical terms that the user won't encounter in the documentation or among messages that Git outputs would be not a good idea. It could confuse users, rather than help them.
Conversely, perhaps we should add Documentation/technical/glossary.txt to help developers.
P.S. By the way, when looking at Documentation/glossary-content.txt, I have noticed few obsolescent entries, like "Git archive", few that have description that soon could be or is obsolete and would need updating, like "master" (when default branch switch to "main"), or "object identifier" and "SHA-1" (when Git switches away from SHA-1 as hash function).
Best,
--
Jakub Narębski
Re: Extending and updating gitglossary
Quoted 6 lines · Jakub Narębski
I don't think adding technical terms that the user won't encounter in the documentation or among messages that Git outputs would be not a good idea. It could confuse users, rather than help them.
Conversely, perhaps we should add
Documentation/technical/glossary.txtto help developers.
Thanks for a thoughtful suggestion to help the target audience. I agree 100% with the above two paragraphs.
Re: Extending and updating gitglossary (was: Re: [PATCH v4 06/10] commit-graph: implement corrected commit date)
In reply to Jakub NarębskiHi Jakub,
Quoted 26 lines · Jakub Narębski
Quoted 25 lines · Junio C Hamano
Quoted 10 lines · Philip Oakley
This may be not part of the the main project, but could you consider, if time permits, also adding some entries into the Git Glossary (
git help glossary) for the various terms we are using here and elsewhere, e.g. 'topological levels', 'generation number', 'corrected commit date' (and its fancy technical name for the use of date heuristics e.g. the 'chronological ordering';).The glossary can provide a reference, once the issues are resolved. The History Simplification and Commit Ordering section of git-log maybe a useful guide to some of the terms that would link to the glossary.
Ah, I first thought that
Documentation/rev-list-options.txt(which is the relevant part of "git log" documentation you mention here) already have references to deep technical terms explained in the glossary and you are suggesting Abhishek to mimic the arrangement by adding new and agreed-upon terms to the glossary and referring to them from the commit-graph documentation updated by this series.But sadly that is not the case. What you are saying is that you noticed that rev-list-options.txt needs a similar "the terms we use to explain these two sections should be defined and explained in the glossary (if they are not) and new references to glossary should be added there" update.
Quoted 8 lines
Quoted 3 lines
In any case, that is a very good suggestion. I agree that updating "git log" doc may be outside the scope of Abhishek's theme, but it would be very good to have such an update by anybody ;-)
The only possible problem I see with this suggestion is that some of those terms (like 'topological levels' and 'corrected commit date') are technical terms that should be not of concern for Git user, only for developers working on Git. (However one could encounter the term "generation number" in
git commit-graph verifyoutput.)
However we do mention "topolog*" in a number of the manual pages, and rather less, as yet, in the technical pages.
"Lexicographic" and "chronological" are in the same group of fancy technical words ;-)
Quoted 7 lines
I don't think adding technical terms that the user won't encounter in the documentation or among messages that Git outputs would be not a good idea. It could confuse users, rather than help them.
Conversely, perhaps we should add
Documentation/technical/glossary.txtto help developers.
I would agree that the Glossary probably ought to be split into the primary, secondary and background terms so that the core concepts are separated from the academic/developer style terms.
Git does rip up most of what folks think about version "control", usually based on the imperfect replication of physical artefacts.
Quoted 7 lines
P.S. By the way, when looking atDocumentation/glossary-content.txt, I have noticed few obsolescent entries, like "Git archive", few that have description that soon could be or is obsolete and would need updating, like "master" (when default branch switch to "main"), or "object identifier" and "SHA-1" (when Git switches away from SHA-1 as hash function).
The obsolescent items can be updated. I'm expecting that the 'main' and 'SHA-' changes will eventually be picked up as part of the respective patch series, hopefully as part of the global replacements.
--
Philip
Re: Extending and updating gitglossary
Hello Philip,
Quoted 22 lines · Philip Oakley
Quoted 21 lines · Jakub Narębski
Quoted 20 lines · Junio C Hamano
Quoted 10 lines · Philip Oakley
This may be not part of the the main project, but could you consider, if time permits, also adding some entries into the Git Glossary (
git help glossary) for the various terms we are using here and elsewhere, e.g. 'topological levels', 'generation number', 'corrected commit date' (and its fancy technical name for the use of date heuristics e.g. the 'chronological ordering';).The glossary can provide a reference, once the issues are resolved. The History Simplification and Commit Ordering section of git-log maybe a useful guide to some of the terms that would link to the glossary.
Ah, I first thought that
Documentation/rev-list-options.txt(which is the relevant part of "git log" documentation you mention here) already have references to deep technical terms explained in the glossary and you are suggesting Abhishek to mimic the arrangement by adding new and agreed-upon terms to the glossary and referring to them from the commit-graph documentation updated by this series.
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
But sadly that is not the case. What you are saying is that you noticed that rev-list-options.txt needs a similar "the terms we use to explain these two sections should be defined and explained in the glossary (if they are not) and new references to glossary should be added there" update.
What terms you feel need glossary entry?
Quoted 9 lines
Quoted 9 lines
Quoted 3 lines
In any case, that is a very good suggestion. I agree that updating "git log" doc may be outside the scope of Abhishek's theme, but it would be very good to have such an update by anybody ;-)
The only possible problem I see with this suggestion is that some of those terms (like 'topological levels' and 'corrected commit date') are technical terms that should be not of concern for Git user, only for developers working on Git. (However one could encounter the term "generation number" in
git commit-graph verifyoutput.)
To be more precise, I think that user-facing glossary should include only terms that appear in user-facing documentation and in output messages of Git commands (with the possible exception of maybe output messages of some low-level plumbing).
I think that the developer-facing glossary should include terms that appear in technical documentation, and in commit messages in Git history.
Quoted 5 lines
However we do mention "topolog*" in a number of the manual pages, and rather less, as yet, in the technical pages.
"Lexicographic" and "chronological" are in the same group of fancy technical words ;-)
I think that 'topological level' would appear only in technical documentation; if it would be the case then there is no reason to add it to user-facing glossary (to gitglossary manpage).
'Topological order' or 'topological sort', 'lexicographical order' and 'chronological order' are not Git-specific terms, and there are no Git-specific ambiguities. I am therefore a bit unsure about adding them to *Git* glossary.
- In computer science, a _topological sort_ or _topological_ ordering of a directed graph is a linear ordering of its vertices such that for every directed edge uv from vertex u to vertex v, u comes before v in the ordering.
For Git it means that top to bottom, commits always appear before their parents. With --graph or --topo-order Git also avoids showing commits on multiple lines of history intermixed.
- In mathematics, the _lexicographic_ or _lexicographical order_ (also known as lexical order, dictionary order, etc.) is a generalization of the alphabetical order.
For Git it is simply alphabetical order.
- _Chronological order_ is the arrangement of things following one after another in time; or in other words date order.
Note that git log --date-order commits also always appear before their parents, but otherwise commits are shown in the commit timestamp order (committer date order)
Quoted 11 lines
Quoted 7 lines
I don't think adding technical terms that the user won't encounter in the documentation or among messages that Git outputs would be not a good idea. It could confuse users, rather than help them.
Conversely, perhaps we should add
Documentation/technical/glossary.txtto help developers.I would agree that the Glossary probably ought to be split into the primary, secondary and background terms so that the core concepts are separated from the academic/developer style terms.
I don't thing we need three separate layers; in my opinion separating terms that user of Git might encounter from terms that somebody working on developing Git may encounter would be enough.
The technical glossary / dictionary could also help onboarding...
Quoted 3 lines
Git does rip up most of what folks think about version "control", usually based on the imperfect replication of physical artefacts.
I don't quite understand what you wanted to say there. Could you explain in more detail, please?
Quoted 10 lines
Quoted 6 lines
P.S. By the way, when looking atDocumentation/glossary-content.txt, I have noticed few obsolescent entries, like "Git archive", few that have description that soon could be or is obsolete and would need updating, like "master" (when default branch switch to "main"), or "object identifier" and "SHA-1" (when Git switches away from SHA-1 as hash function).The obsolescent items can be updated. I'm expecting that the 'main' and 'SHA-' changes will eventually be picked up as part of the respective patch series, hopefully as part of the global replacements.
Here I meant that "Git archive" entry is not important anymore, as I think there are no active users of GNU arch version control system (no "arch people"); arch's last release was in 2006, and its replacement, Bazaar (or 'bzr') doesn't use this term. So I think it can be safely removed in 2020, after 14 years after last release of arch.
In most cases "SHA-1" in the descriptions of terms in glossary should be replaced by "object identifier" (to be more generic). This can be safely done before switch to NewHash is ready and announced.
Best,
Signature · 1 line
Jakub Narębski
Re: Extending and updating gitglossary
Hi Jakub,
Quoted 24 lines · Jakub Narębski
Hello Philip,
Quoted 21 lines · Philip Oakley
Quoted 20 lines · Jakub Narębski
Quoted 19 lines · Junio C Hamano
Quoted 10 lines · Philip Oakley
This may be not part of the the main project, but could you consider, if time permits, also adding some entries into the Git Glossary (
git help glossary) for the various terms we are using here and elsewhere, e.g. 'topological levels', 'generation number', 'corrected commit date' (and its fancy technical name for the use of date heuristics e.g. the 'chronological ordering';).The glossary can provide a reference, once the issues are resolved. The History Simplification and Commit Ordering section of git-log maybe a useful guide to some of the terms that would link to the glossary.
Ah, I first thought that
Documentation/rev-list-options.txt(which is the relevant part of "git log" documentation you mention here) already have references to deep technical terms explained in the glossary and you are suggesting Abhishek to mimic the arrangement by adding new and agreed-upon terms to the glossary and referring to them from the commit-graph documentation updated by this series.
Quoted 6 lines
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
But sadly that is not the case. What you are saying is that you noticed that rev-list-options.txt needs a similar "the terms we use to explain these two sections should be defined and explained in the glossary (if they are not) and new references to glossary should be added there" update.
What terms you feel need glossary entry?
While it was Junio that made the comment, I'd agree that we should be using the glossary to explain, in a general sense, the terms that are used is a specialist sense. As the user community expands, their natural understanding of some of the terms diminishes.
Quoted 9 lines
Quoted 9 lines
Quoted 9 lines
Quoted 4 lines
In any case, that is a very good suggestion. I agree that updating "git log" doc may be outside the scope of Abhishek's theme, but it would be very good to have such an update by anybody ;-)
The only possible problem I see with this suggestion is that some of those terms (like 'topological levels' and 'corrected commit date') are technical terms that should be not of concern for Git user, only for developers working on Git. (However one could encounter the term "generation number" in
git commit-graph verifyoutput.)
Quoted 4 lines
To be more precise, I think that user-facing glossary should include only terms that appear in user-facing documentation and in output messages of Git commands (with the possible exception of maybe output messages of some low-level plumbing).
And where implied, the underlying concepts when they aren't obvious, or lack general terms (e.g. the 'staging area' discussions)
Quoted 10 lines
I think that the developer-facing glossary should include terms that appear in technical documentation, and in commit messages in Git history.
Quoted 5 lines
However we do mention "topolog*" in a number of the manual pages, and rather less, as yet, in the technical pages.
"Lexicographic" and "chronological" are in the same group of fancy technical words ;-)
Quoted 8 lines
I think that 'topological level' would appear only in technical documentation; if it would be the case then there is no reason to add it to user-facing glossary (to gitglossary manpage).
'Topological order' or 'topological sort', 'lexicographical order' and 'chronological order' are not Git-specific terms, and there are no Git-specific ambiguities. I am therefore a bit unsure about adding them to *Git* glossary.
It is that they aren't terms used in normal speech, so many folks do not comprehend the implied precision that the docs assume, nor the problems they may hide.
Quoted 5 lines
- In computer science, a
_topologicalsort_or _topological_ ordering of a directed graph is a linear ordering of its vertices such that for every directed edge uv from vertex u to vertex v, u comes before v in the ordering.
Does this imply that those who aren't computer scientists shouldn't be using Git?
Quoted 10 lines
For Git it means that top to bottom, commits always appear before their parents. With
--graphor--topo-orderGit also avoids showing commits on multiple lines of history intermixed.- In mathematics, the _lexicographic_ or
_lexicographicalorder_(also known as lexical order, dictionary order, etc.) is a generalization of the alphabetical order.For Git it is simply alphabetical order.
ASCII order, Case sensitivity, Special characters, etc.
Quoted 3 lines
-
_Chronologicalorder_is the arrangement of things following one after another in time; or in other words date order.
Given that most résumés (the thing most folk see that asks for date order) is latest first, does this clarify which way chronological is? (I see this regularly in my other volunteer work).
Quoted 19 lines
Note that
git log --date-ordercommits also always appear before their parents, but otherwise commits are shown in the commit timestamp order (committer date order)Quoted 9 lines
Quoted 6 lines
I don't think adding technical terms that the user won't encounter in the documentation or among messages that Git outputs would be not a good idea. It could confuse users, rather than help them.
Conversely, perhaps we should add
Documentation/technical/glossary.txtto help developers.I would agree that the Glossary probably ought to be split into the primary, secondary and background terms so that the core concepts are separated from the academic/developer style terms.
I don't thing we need three separate layers; in my opinion separating terms that user of Git might encounter from terms that somebody working on developing Git may encounter would be enough.
Quoted 6 lines
The technical glossary / dictionary could also help onboarding...
Quoted 2 lines
Git does rip up most of what folks think about version "control", usually based on the imperfect replication of physical artefacts.
I don't quite understand what you wanted to say there. Could you explain in more detail, please?
Background, I see Git & Version Control from an engineers view point, rather than developers view.
In the "real" world there are no perfect copies, we serialise key items so that we can track their degradation, and replace them when required. We attempt to "Control" what is happening. Our documentation and monitoring systems have layers of control to ensure only suitably qualified persons may access and inspect critical items, can record and access previous status reports, etc. There is only one "Mona Lisa", with critical access controls, even though there are 'copies'
https://en.wikipedia.org/wiki/Mona_Lisa#Early_versions_and_copies.
Almost all of our terminology for configuration control comes from the 'real' world, i.e. pre-modern computing.
Git turns all that on its head. We can make perfect duplicates (they're not copies, not replicas..). The Object name is immutable. It's either right or wrong (exempt the SHAttered sha-1 breakage; were moving to sha-256). Git does *not* provide any access control. It supports the 'software freedoms' by distributing the control to the user. The repository is a version storage system, and the OIDs allow easy authentication between folks that they are looking at the same object, and all its implied descendants.
Git has ripped up classical 'real' world version control. In many areas we need new or alternative terms, and documents that explain them to screen writers(*) and the many other non CS-major users of Git (and some engineers;-)
(*) there's a diff pattern for them, IIRC, or at least one was proposed.
Quoted 16 lines
Quoted 10 lines
Quoted 7 lines
P.S. By the way, when looking atDocumentation/glossary-content.txt, I have noticed few obsolescent entries, like "Git archive", few that have description that soon could be or is obsolete and would need updating, like "master" (when default branch switch to "main"), or "object identifier" and "SHA-1" (when Git switches away from SHA-1 as hash function).The obsolescent items can be updated. I'm expecting that the 'main' and 'SHA-' changes will eventually be picked up as part of the respective patch series, hopefully as part of the global replacements.
Here I meant that "Git archive" entry is not important anymore, as I think there are no active users of GNU arch version control system (no "arch people"); arch's last release was in 2006, and its replacement, Bazaar (or 'bzr') doesn't use this term. So I think it can be safely removed in 2020, after 14 years after last release of arch.
Quoted 5 lines
In most cases "SHA-1" in the descriptions of terms in glossary should be replaced by "object identifier" (to be more generic). This can be safely done before switch to NewHash is ready and announced.
Best,
--
Philip
Re: Extending and updating gitglossary
Hello Philip,
Quoted 16 lines · Philip Oakley
Quoted 15 lines · Jakub Narębski
Quoted 14 lines · Philip Oakley
Quoted 13 lines · Jakub Narębski
Quoted 12 lines · Junio C Hamano
Quoted 10 lines · Philip Oakley
This may be not part of the the main project, but could you consider, if time permits, also adding some entries into the Git Glossary (
git help glossary) for the various terms we are using here and elsewhere, e.g. 'topological levels', 'generation number', 'corrected commit date' (and its fancy technical name for the use of date heuristics e.g. the 'chronological ordering';).The glossary can provide a reference, once the issues are resolved. The History Simplification and Commit Ordering section of git-log maybe a useful guide to some of the terms that would link to the glossary.
[...]
Quoted 6 lines
Quoted 1 line
What terms you feel need glossary entry?
While it was Junio that made the comment, I'd agree that we should be using the glossary to explain, in a general sense, the terms that are used is a specialist sense. As the user community expands, their natural understanding of some of the terms diminishes.
I was hoping for a list of terms from the abovementioned sections of git-log manpage you feel need entry in gitglosary(7).
[...]
Quoted 7 lines
Quoted 4 lines
To be more precise, I think that user-facing glossary should include only terms that appear in user-facing documentation and in output messages of Git commands (with the possible exception of maybe output messages of some low-level plumbing).
And where implied, the underlying concepts when they aren't obvious, or lack general terms (e.g. the 'staging area' discussions)
True, 'staging area' should IMVHO be in glossary (replacing or in addition to older less specific term 'index', previous name for 'staging area' term).
Quoted 3 lines
Quoted 3 lines
I think that the developer-facing glossary should include terms that appear in technical documentation, and in commit messages in Git history.
Such as 'topological levels', 'commit slab' / 'on the slab', etc.
Quoted 10 lines
Quoted 10 lines
Quoted 5 lines
However we do mention "topolog*" in a number of the manual pages, and rather less, as yet, in the technical pages.
"Lexicographic" and "chronological" are in the same group of fancy technical words ;-)
I think that 'topological level' would appear only in technical documentation; if it would be the case then there is no reason to add it to user-facing glossary (to gitglossary manpage).
Quoted 8 lines
Quoted 4 lines
'Topological order' or 'topological sort', 'lexicographical order' and 'chronological order' are not Git-specific terms, and there are no Git-specific ambiguities. I am therefore a bit unsure about adding them to *Git* glossary.
It is that they aren't terms used in normal speech, so many folks do not comprehend the implied precision that the docs assume, nor the problems they may hide.
Right.
Quoted 7 lines
Quoted 4 lines
- In computer science, a
_topologicalsort_or _topological_ ordering of a directed graph is a linear ordering of its vertices such that for every directed edge uv from vertex u to vertex v, u comes before v in the ordering.Does this imply that those who aren't computer scientists shouldn't be using Git?
I think that in most cases where we refer to topological order in the documentation we describe it there. It might be good idea to add it to the glossary, especially because Git uses it often in a very specific sense.
On the other hand, should we define 'topology' or 'graph' as well? Or 'glossary' ;-) ? Those don't have any special meaning in Git, and can be as well found in the dictionary or Wikipedia.
Quoted 11 lines
Quoted 9 lines
For Git it means that top to bottom, commits always appear before their parents. With
--graphor--topo-orderGit also avoids showing commits on multiple lines of history intermixed.- In mathematics, the _lexicographic_ or
_lexicographicalorder_(also known as lexical order, dictionary order, etc.) is a generalization of the alphabetical order.For Git it is simply alphabetical order.
ASCII order, Case sensitivity, Special characters, etc.
Actually I don't know. Let me check: the only place this term appears in the documentation is in git-tag(1) manpage and related documentation. It simplly uses strcmp(), or strcasecmp() when using --ignore-case option; so by default case sensitive.
It looks like it does not take locale-specific rules.
Quoted 6 lines
Quoted 2 lines
-
_Chronologicalorder_is the arrangement of things following one after another in time; or in other words date order.Given that most résumés (the thing most folk see that asks for date order) is latest first, does this clarify which way chronological is? (I see this regularly in my other volunteer work).
Right, it might be not obvious at first glance that Git outputs most recent commits first, that is newest commits are on top. Though if you think about it in more detail, it is the only ordering that makes sense, especially for projects with a long history; first, it is newest commits that are most interesting, and second Git always walks the history from child to parent.
Quoted 3 lines
Quoted 3 lines
Note that
git log --date-ordercommits also always appear before their parents, but otherwise commits are shown in the commit timestamp order (committer date order)
[...]
Quoted 29 lines
Quoted 5 lines
Quoted 2 lines
Git does rip up most of what folks think about version "control", usually based on the imperfect replication of physical artefacts.
I don't quite understand what you wanted to say there. Could you explain in more detail, please?
Background, I see Git & Version Control from an engineers view point, rather than developers view.
In the "real" world there are no perfect copies, we serialise key items so that we can track their degradation, and replace them when required. We attempt to "Control" what is happening. Our documentation and monitoring systems have layers of control to ensure only suitably qualified persons may access and inspect critical items, can record and access previous status reports, etc. There is only one "Mona Lisa", with critical access controls, even though there are 'copies'
https://en.wikipedia.org/wiki/Mona_Lisa#Early_versions_and_copies.
Almost all of our terminology for configuration control comes from the 'real' world, i.e. pre-modern computing.Git turns all that on its head. We can make perfect duplicates (they're not copies, not replicas..). The Object name is immutable. It's either right or wrong (exempt the SHAttered sha-1 breakage; were moving to sha-256). Git does *not* provide any access control. It supports the 'software freedoms' by distributing the control to the user. The repository is a version storage system, and the OIDs allow easy authentication between folks that they are looking at the same object, and all its implied descendants.
Quoted 6 lines
Git has ripped up classical 'real' world version control. In many areas we need new or alternative terms, and documents that explain them to screen
writers(*)and the many other non CS-major users of Git (and some engineers;-)(*) there's a diff pattern for them, IIRC, or at least one was proposed.
Right, though for me the concept of 'version control' was by default always about the digital, usually the source code.
There are different editions of books, changes to non-digital technical drawings and plans (AFAIK often in the form of physical foil overlays as subsequent layers, if done well; overdrawing on the same layer if not), amendment and changes to laws, etc.
Anyway, the question is what level of knowledge can we assume from the average Git user -- this would affect the spread of terms that should be considered for the Git glossary.
Best,
Signature · 1 line
Jakub Narębski
PATCH v4 7/10 commit-graph: implement generation data chunk
In reply to Abhishek Kumar via GitGitGadgetFrom: Abhishek Kumar <[email protected]>
As discovered by Ævar, we cannot increment graph version to distinguish between generation numbers v1 and v2 [1]. Thus, one of pre-requistes before implementing generation number was to distinguish between graph versions in a backwards compatible manner.
We are going to introduce a new chunk called Generation Data chunk (or GDAT). GDAT stores corrected committer date offsets whereas CDAT will still store topological level.
Old Git does not understand GDAT chunk and would ignore it, reading topological levels from CDAT. New Git can parse GDAT and take advantage of newer generation numbers, falling back to topological levels when GDAT chunk is missing (as it would happen with a commit graph written by old Git).
We introduce a test environment variable 'GIT_TEST_COMMIT_GRAPH_NO_GDAT' which forces commit-graph file to be written without generation data chunk to emulate a commit-graph file written by old Git.
While storing corrected commit date offset instead of the corrected commit date saves us 4 bytes per commit, it's possible for the offsets to overflow the 4-bytes allocated. As such overflows are exceedingly rare, we use the following overflow management scheme:
We introduce a new commit-graph chunk, GENERATION_DATA_OVERFLOW ('GDOV') to store corrected commit dates for commits with offsets greater than GENERATION_NUMBER_V2_OFFSET_MAX.
If the offset is greater than GENERATION_NUMBER_V2_OFFSET_MAX, we set the MSB of the offset and the other bits store the position of corrected commit date in GDOV chunk, similar to how Extra Edge List is maintained.
We test the overflow-related code with the following repo history:
F - N - U
/ \
U - N - U N
\ /
N - F - N
Where the commits denoted by U have committer date of zero seconds since Unix epoch, the commits denoted by N have committer date of 1112354055 (default committer date for the test suite) seconds since Unix epoch and the commits denoted by F have committer date of (2 ^ 31 - 2) seconds since Unix epoch.
The largest offset observed is 2 ^ 31, just large enough to overflow.
[1]: https://lore.kernel.org/git/[email protected]/
--- commit-graph.c | 98 +++++++++++++++++++++++++++++++++-- commit-graph.h | 3 ++ commit.h | 1 + t/README | 3 ++ t/helper/test-read-graph.c | 4 ++ t/t4216-log-bloom.sh | 4 +- t/t5318-commit-graph.sh | 70 ++++++++++++++++++++----- t/t5324-split-commit-graph.sh | 12 ++--- t/t6600-test-reach.sh | 68 +++++++++++++----------- 9 files changed, 206 insertions(+), 57 deletions(-)
commit-graph.c +93 -5
@@ -38,11 +38,13 @@ void git_test_write_commit_graph_or_die(void)#define GRAPH_CHUNKID_OIDFANOUT 0x4f494446/* "OIDF" */ #define GRAPH_CHUNKID_OIDLOOKUP 0x4f49444c/* "OIDL" */ #define GRAPH_CHUNKID_DATA 0x43444154/* "CDAT" */ +#define GRAPH_CHUNKID_GENERATION_DATA 0x47444154/* "GDAT" */ +#define GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW 0x47444f56/* "GDOV" */ #define GRAPH_CHUNKID_EXTRAEDGES 0x45444745/* "EDGE" */ #define GRAPH_CHUNKID_BLOOMINDEXES 0x42494458/* "BIDX" */ #define GRAPH_CHUNKID_BLOOMDATA 0x42444154/* "BDAT" */ #define GRAPH_CHUNKID_BASE 0x42415345/* "BASE" */ -#define MAX_NUM_CHUNKS 7 +#define MAX_NUM_CHUNKS 9#define GRAPH_DATA_WIDTH (the_hash_algo->rawsz + 16)
@@ -61,6 +63,8 @@ void git_test_write_commit_graph_or_die(void)#define GRAPH_MIN_SIZE (GRAPH_HEADER_SIZE + 4 * GRAPH_CHUNKLOOKUP_WIDTH \ + GRAPH_FANOUT_SIZE + the_hash_algo->rawsz) +#define CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW (1ULL << 31) +/* Remember to update object flag allocation in object.h */ #define REACHABLE (1u<<15)
@@ -385,6 +389,20 @@ struct commit_graph *parse_commit_graph(struct repository *r, graph->chunk_commit_data = data + chunk_offset;break ; +case GRAPH_CHUNKID_GENERATION_DATA : +if (graph->chunk_generation_data) + chunk_repeated =1 ; +else + graph->chunk_generation_data = data + chunk_offset; +break ; + +case GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW : +if (graph->chunk_generation_data_overflow) + chunk_repeated =1 ; +else + graph->chunk_generation_data_overflow = data + chunk_offset; +break ; +case GRAPH_CHUNKID_EXTRAEDGES :if (graph->chunk_extra_edges) chunk_repeated =1 ;
@@ -745,8 +763,8 @@ static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, {const unsigned char *commit_data;struct commit_graph_data *graph_data; -uint32_t lex_index; -uint64_t date_high, date_low; +uint32_t lex_index, offset_pos; +uint64_t date_high, date_low, offset;while (pos < g->num_commits_in_base) g = g->base_graph;
@@ -764,7 +782,16 @@ static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, date_low =get_be32 (commit_data + g->hash_len +12 ); item->date = (timestamp_t)((date_high <<32 ) | date_low); - graph_data->generation =get_be32 (commit_data + g->hash_len +8 ) >>2 ; +if (g->chunk_generation_data) { + offset = (timestamp_t )get_be32 (g->chunk_generation_data +sizeof (uint32_t ) * lex_index); + +if (offset &CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW ) { + offset_pos = offset ^CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW ; + graph_data->generation =get_be64 (g->chunk_generation_data_overflow +8 * offset_pos); + }else + graph_data->generation = item->date + offset; + }else + graph_data->generation =get_be32 (commit_data + g->hash_len +8 ) >>2 ;if (g->topo_levels) *topo_level_slab_at (g->topo_levels, item) =get_be32 (commit_data + g->hash_len +8 ) >>2 ;
@@ -942,6 +969,7 @@ struct write_commit_graph_context {struct packed_oid_list oids;struct packed_commit_list commits;int num_extra_edges; +int num_generation_data_overflows;unsigned long approx_nr_objects;struct progress *progress;int progress_done;
@@ -960,7 +988,8 @@ struct write_commit_graph_context { report_progress:1, split:1, changed_paths:1, - order_by_pack:1; + order_by_pack:1, + write_generation_data:1;struct topo_level_slab *topo_levels;const struct commit_graph_opts *opts;
@@ -1120,6 +1149,44 @@ static int write_graph_chunk_data(struct hashfile *f,return 0 ; } +static int write_graph_chunk_generation_data (struct hashfile *f, +struct write_commit_graph_context *ctx) +{ +int i, num_generation_data_overflows =0 ; +for (i =0 ; i < ctx->commits.nr; i++) { +struct commit *c = ctx->commits.list[i]; +timestamp_t offset =commit_graph_data_at (c)->generation - c->date; +display_progress (ctx->progress, ++ctx->progress_cnt); + +if (offset >GENERATION_NUMBER_V2_OFFSET_MAX ) { + offset =CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW | num_generation_data_overflows; + num_generation_data_overflows++; + } + +hashwrite_be32 (f, offset); + } + +return 0 ; +} + +static int write_graph_chunk_generation_data_overflow (struct hashfile *f, +struct write_commit_graph_context *ctx) +{ +int i; +for (i =0 ; i < ctx->commits.nr; i++) { +struct commit *c = ctx->commits.list[i]; +timestamp_t offset =commit_graph_data_at (c)->generation - c->date; +display_progress (ctx->progress, ++ctx->progress_cnt); + +if (offset >GENERATION_NUMBER_V2_OFFSET_MAX ) { +hashwrite_be32 (f, offset >>32 ); +hashwrite_be32 (f, (uint32_t ) offset); + } + } + +return 0 ; +} +static int write_graph_chunk_extra_edges (struct hashfile *f,struct write_commit_graph_context *ctx) {
@@ -1399,7 +1466,11 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx)if (current->date && current->date > max_corrected_commit_date) max_corrected_commit_date = current->date -1 ; +commit_graph_data_at (current)->generation = max_corrected_commit_date +1 ; + +if (commit_graph_data_at (current)->generation - current->date >GENERATION_NUMBER_V2_OFFSET_MAX ) + ctx->num_generation_data_overflows++; } } }
@@ -1765,6 +1836,21 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx) chunks[2 ].id =GRAPH_CHUNKID_DATA ; chunks[2 ].size = (hashsz +16 ) * ctx->commits.nr; chunks[2 ].write_fn = write_graph_chunk_data; + +if (git_env_bool (GIT_TEST_COMMIT_GRAPH_NO_GDAT ,0 )) + ctx->write_generation_data =0 ; +if (ctx->write_generation_data) { + chunks[num_chunks].id =GRAPH_CHUNKID_GENERATION_DATA ; + chunks[num_chunks].size =sizeof (uint32_t ) * ctx->commits.nr; + chunks[num_chunks].write_fn = write_graph_chunk_generation_data; + num_chunks++; + } +if (ctx->num_generation_data_overflows) { + chunks[num_chunks].id =GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW ; + chunks[num_chunks].size =sizeof (timestamp_t) * ctx->num_generation_data_overflows; + chunks[num_chunks].write_fn = write_graph_chunk_generation_data_overflow; + num_chunks++; + }if (ctx->num_extra_edges) { chunks[num_chunks].id =GRAPH_CHUNKID_EXTRAEDGES ; chunks[num_chunks].size =4 * ctx->num_extra_edges;
@@ -2170,6 +2256,8 @@ int write_commit_graph(struct object_directory *odb, ctx->split = flags &COMMIT_GRAPH_WRITE_SPLIT ?1 :0 ; ctx->opts = opts; ctx->total_bloom_filter_data_size =0 ; + ctx->write_generation_data =1 ; + ctx->num_generation_data_overflows =0 ; bloom_settings.bits_per_entry =git_env_ulong ("GIT_TEST_BLOOM_SETTINGS_BITS_PER_ENTRY" , bloom_settings.bits_per_entry);
commit-graph.h +3 -0
@@ -6,6 +6,7 @@#include "oidset.h" #define GIT_TEST_COMMIT_GRAPH "GIT_TEST_COMMIT_GRAPH" +#define GIT_TEST_COMMIT_GRAPH_NO_GDAT "GIT_TEST_COMMIT_GRAPH_NO_GDAT"#define GIT_TEST_COMMIT_GRAPH_DIE_ON_PARSE "GIT_TEST_COMMIT_GRAPH_DIE_ON_PARSE"#define GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS "GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS"
@@ -68,6 +69,8 @@ struct commit_graph {const uint32_t *chunk_oid_fanout;const unsigned char *chunk_oid_lookup;const unsigned char *chunk_commit_data; +const unsigned char *chunk_generation_data; +const unsigned char *chunk_generation_data_overflow;const unsigned char *chunk_extra_edges;const unsigned char *chunk_base_graphs;const unsigned char *chunk_bloom_indexes;
commit.h +1 -0
@@ -14,6 +14,7 @@#define GENERATION_NUMBER_INFINITY ((1ULL << 63) - 1)#define GENERATION_NUMBER_V1_MAX 0x3FFFFFFF#define GENERATION_NUMBER_ZERO 0 +#define GENERATION_NUMBER_V2_OFFSET_MAX ((1ULL << 31) - 1)struct commit_list {struct commit *item;
t/README +3 -0
@@ -379,6 +379,9 @@ GIT_TEST_COMMIT_GRAPH=<boolean>, when true, forces the commit-graph to be written after every 'git commit' command, and overrides the 'core.commitGraph' setting to true. +GIT_TEST_COMMIT_GRAPH_NO_GDAT=<boolean>, when true, forces the +commit-graph to be written without generation data chunk. + GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=<boolean>, when true, forces commit-graph write to compute and write changed path Bloom filters for every 'git commit-graph write', as if the `--changed-paths` option was
t/helper/test-read-graph.c +4 -0
@@ -33,6 +33,10 @@ int cmd__read_graph(int argc, const char **argv)printf (" oid_lookup" );if (graph->chunk_commit_data)printf (" commit_metadata" ); +if (graph->chunk_generation_data) +printf (" generation_data" ); +if (graph->chunk_generation_data_overflow) +printf (" generation_data_overflow" );if (graph->chunk_extra_edges)printf (" extra_edges" );if (graph->chunk_bloom_indexes)
t/t4216-log-bloom.sh +2 -2
@@ -40,11 +40,11 @@ test_expect_success 'setup test - repo, commits, commit graph, log outputs' ' ' graph_read_expect () { - NUM_CHUNKS=5 + NUM_CHUNKS=6 cat >expect <<- EOF header: 43475048 1 $(test_oid oid_version) $NUM_CHUNKS 0 num_commits: $1 - chunks: oid_fanout oid_lookup commit_metadata bloom_indexes bloom_data + chunks: oid_fanout oid_lookup commit_metadata generation_data bloom_indexes bloom_data EOF test-tool read-graph >actual && test_cmp expect actual
t/t5318-commit-graph.sh +57 -13
146 lines not on this page · raw message
t/t5324-split-commit-graph.sh +6 -6
@@ -13,11 +13,11 @@ test_expect_success 'setup repo' ' infodir=".git/objects/info" && graphdir="$infodir/commit-graphs" && test_oid_cache <<-EOM - shallow sha1:1760 - shallow sha256:2064 + shallow sha1:2132 + shallow sha256:2436 - base sha1:1376 - base sha256:1496 + base sha1:1408 + base sha256:1528 oid_version sha1:1 oid_version sha256:2
@@ -31,9 +31,9 @@ graph_read_expect() { NUM_BASE=$2 fi cat >expect <<- EOF - header: 43475048 1 $(test_oid oid_version) 3 $NUM_BASE + header: 43475048 1 $(test_oid oid_version) 4 $NUM_BASE num_commits: $1 - chunks: oid_fanout oid_lookup commit_metadata + chunks: oid_fanout oid_lookup commit_metadata generation_data EOF test-tool read-graph >output && test_cmp expect output
t/t6600-test-reach.sh +37 -31
242 lines not on this page · raw message
Signature · 1 line
gitgitgadget
Re: [PATCH v4 07/10] commit-graph: implement generation data chunk
Tl;dr summary: the code writing GDOV chunk could be made more performant (I think), but that could be left for the future commit.
Quoted 6 lines · Abhishek Kumar via GitGitGadget
From: Abhishek Kumar <[email protected]>
As discovered by Ævar, we cannot increment graph version to distinguish between generation numbers v1 and v2 [1]. Thus, one of pre-requistes before implementing generation number was to distinguish between graph versions in a backwards compatible manner.
Minor nitpick: I think you meant "implementing generation number v2", to be more precise.
Quoted 2 lines
We are going to introduce a new chunk called Generation Data chunk (or
Very minor nitpick: perhaps s/Generation Data/Generation DATa/, to provide mnemonics for chunk name.
Quoted 2 lines
GDAT). GDAT stores corrected committer date offsets whereas CDAT will still store topological level.
Minor nitpick: I think the second sentence should use consistent grammatical tense (but I am not a native English speaker); also s/level/levels/:
GDAT will store corrected committer date offsets, whereas CDAT will still store topological levels.
But it is perfectly understandable as it is.
Quoted 6 lines
Old Git does not understand GDAT chunk and would ignore it, reading topological levels from CDAT. New Git can parse GDAT and take advantage of newer generation numbers, falling back to topological levels when GDAT chunk is missing (as it would happen with a commit graph written by old Git).
Minor nitpick: I think we use commit-graph with dash when writing about the commit-graph file, like below.
Quoted 4 lines
We introduce a test environment variable '
GIT_TEST_COMMIT_GRAPH_NO_GDAT' which forces commit-graph file to be written without generation data chunk to emulate a commit-graph file written by old Git.
All right.
Quoted 5 lines
While storing corrected commit date offset instead of the corrected commit date saves us 4 bytes per commit, it's possible for the offsets to overflow the 4-bytes allocated. As such overflows are exceedingly rare, we use the following overflow management scheme:
Perhaps it would be good idea to write the idea in full from start, as the commit message is intended to be read stadalone and not in the context of the patch series. On the other hand it might be too much detail in already [necessarily] lengthty commit message.
Perhaps something like the following proposal would read better.
To minimize the space required to store corrected commit date, Git stores corrected commit date offsets into the commit-graph file, instead of corrected commit dates themselves. This saves us 4 bytes per commit, decreasing the GDAT chunk size by half, but it's possible for the offset to overflow the 4-bytes allocated for storage. As such overflows are and should be exceedingly rare, we use the following overflow management scheme:
NOTE: this overflow handling is a *new* code (or new-ish code, as it is inspired and similar to EDGE chunk data handling), so it needs more careful review.
Quoted 2 lines
We introduce a new commit-graph chunk,
GENERATION_DATA_OVERFLOW('GDOV')
Minor issue: why GENERATION_DATA_OVERFLOW and not Generation Data OVerflow, like for the GDAT chunk?
Quoted 7 lines
to store corrected commit dates for commits with offsets greater than
GENERATION_NUMBER_V2_OFFSET_MAX.If the offset is greater than
GENERATION_NUMBER_V2_OFFSET_MAX, we set the MSB of the offset and the other bits store the position of corrected commit date in GDOV chunk, similar to how Extra Edge List is maintained.
Quoted 7 lines
We test the overflow-related code with the following repo history:
F - N - U
/ \
U - N - U N
\ /
N - F - N
Do we need such complex history? I guess we need to test the handling of merge commits too.
Quoted 30 lines
Where the commits denoted by U have committer date of zero seconds since Unix epoch, the commits denoted by N have committer date of 1112354055 (default committer date for the test suite) seconds since Unix epoch and the commits denoted by F have committer date of (2 ^ 31 - 2) seconds since Unix epoch.
The largest offset observed is 2 ^ 31, just large enough to overflow.
[1]: https://lore.kernel.org/git/[email protected]/
Signed-off-by Abhishek Kumar <[email protected]>--- commit-graph.c | 98 +++++++++++++++++++++++++++++++++-- commit-graph.h | 3 ++ commit.h | 1 + t/README | 3 ++ t/helper/test-read-graph.c | 4 ++ t/t4216-log-bloom.sh | 4 +- t/t5318-commit-graph.sh | 70 ++++++++++++++++++++----- t/t5324-split-commit-graph.sh | 12 ++--- t/t6600-test-reach.sh | 68 +++++++++++++----------- 9 files changed, 206 insertions(+), 57 deletions(-)commit-graph.c +0
-0@@ -38,11 +38,13 @@ void git_test_write_commit_graph_or_die(void)#define GRAPH_CHUNKID_OIDFANOUT 0x4f494446/* "OIDF" */
commit-graph.c void git_test_write_commit_graph_or_die(void) · line 47
Quoted 10 lines
commit-graph.c +3
-1#define GRAPH_CHUNKID_OIDLOOKUP 0x4f49444c/* "OIDL" */ #define GRAPH_CHUNKID_DATA 0x43444154/* "CDAT" */ +#define GRAPH_CHUNKID_GENERATION_DATA 0x47444154/* "GDAT" */ +#define GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW 0x47444f56/* "GDOV" */ #define GRAPH_CHUNKID_EXTRAEDGES 0x45444745/* "EDGE" */ #define GRAPH_CHUNKID_BLOOMINDEXES 0x42494458/* "BIDX" */ #define GRAPH_CHUNKID_BLOOMDATA 0x42444154/* "BDAT" */ #define GRAPH_CHUNKID_BASE 0x42415345/* "BASE" */ -#define MAX_NUM_CHUNKS 7 +#define MAX_NUM_CHUNKS 9
All right.
commit-graph.c void git_test_write_commit_graph_or_die(void) · line 67
Quoted 9 lines
commit-graph.c +2
-0#define GRAPH_DATA_WIDTH (the_hash_algo->rawsz + 16)@@ -61,6 +63,8 @@ void git_test_write_commit_graph_or_die(void)#define GRAPH_MIN_SIZE (GRAPH_HEADER_SIZE + 4 * GRAPH_CHUNKLOOKUP_WIDTH \ + GRAPH_FANOUT_SIZE + the_hash_algo->rawsz) +#define CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW (1ULL << 31) +
All right, though the naming convention is different from the one used for EDGE chunk: GRAPH_EXTRA_EDGES_NEEDED and GRAPH_EDGE_LAST_MASK.
Quoted 11 lines
commit-graph.c +4
-0/* Remember to update object flag allocation in object.h */ #define REACHABLE (1u<<15)@@ -385,6 +389,20 @@ struct commit_graph *parse_commit_graph(struct repository *r, graph->chunk_commit_data = data + chunk_offset;break ; +case GRAPH_CHUNKID_GENERATION_DATA : +if (graph->chunk_generation_data) + chunk_repeated =1 ; +else
commit-graph.c struct commit_graph *parse_commit_graph(struct repository *r, · line 405
Quoted 10 lines
commit-graph.c +10
-0+ graph->chunk_generation_data = data + chunk_offset; +break ; + +case GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW : +if (graph->chunk_generation_data_overflow) + chunk_repeated =1 ; +else + graph->chunk_generation_data_overflow = data + chunk_offset; +break ; +
Necessary but unavoidable boilerplate for adding new chunks to the commit-graph file format. All right.
commit-graph.c static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, · line 767
Quoted 11 lines
commit-graph.c +2
-2case GRAPH_CHUNKID_EXTRAEDGES :if (graph->chunk_extra_edges) chunk_repeated =1 ;@@ -745,8 +763,8 @@ static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, {const unsigned char *commit_data;struct commit_graph_data *graph_data; -uint32_t lex_index; -uint64_t date_high, date_low; +uint32_t lex_index, offset_pos; +uint64_t date_high, date_low, offset;
All right, we are adding two new variables: offset to read data stored in GDAT chunk, and offset_pos to help read data from GDOV chunk if necessary i.e. to handle overflow in corrected commit data offset storage.
commit-graph.c static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, · line 786
Quoted 10 lines
commit-graph.c +2
-1while (pos < g->num_commits_in_base) g = g->base_graph;@@ -764,7 +782,16 @@ static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, date_low =get_be32 (commit_data + g->hash_len +12 ); item->date = (timestamp_t)((date_high <<32 ) | date_low); - graph_data->generation =get_be32 (commit_data + g->hash_len +8 ) >>2 ; +if (g->chunk_generation_data) { + offset = (timestamp_t )get_be32 (g->chunk_generation_data +sizeof (uint32_t ) * lex_index);
Style: why space after the (timestamp_t) cast operator?
Though CodingGuidelines do not say anything on this topic... perhaps the space after cast operator makes it more readable?
commit-graph.c static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, · line 788
Quoted 2 lines
commit-graph.c +2
-0+ +if (offset &CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW ) {
All right, so the CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW is equivalent of GRAPH_EXTRA_EDGES_NEEDED.
commit-graph.c static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, · line 789
Quoted 1 line
commit-graph.c +1
-0+ offset_pos = offset ^CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW ;
Hmmm... instead of using bitwise and on an equivalent to the GRAPH_EDGE_LAST_MASK, we utilize the fact that we know that the MSB bit is set, so we can clear it with bitwise xor. Clever trick.
commit-graph.c static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, · line 792
Quoted 3 lines
commit-graph.c +3
-0+ graph_data->generation =get_be64 (g->chunk_generation_data_overflow +8 * offset_pos); + }else + graph_data->generation = item->date + offset;
All right, this handles the case when we have generation number v2, with or without overflow.
commit-graph.c static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, · line 794
Quoted 2 lines
commit-graph.c +2
-0+ }else + graph_data->generation =get_be32 (commit_data + g->hash_len +8 ) >>2 ;
All right, this handles the case where we have only generation number v1, like for commit-graph file written by old Git.
Quoted 12 lines
commit-graph.c +1
-0if (g->topo_levels) *topo_level_slab_at (g->topo_levels, item) =get_be32 (commit_data + g->hash_len +8 ) >>2 ;@@ -942,6 +969,7 @@ struct write_commit_graph_context {struct packed_oid_list oids;struct packed_commit_list commits;int num_extra_edges; +int num_generation_data_overflows;unsigned long approx_nr_objects;struct progress *progress;int progress_done;
commit-graph.c struct write_commit_graph_context { · line 995
Quoted 9 lines
commit-graph.c +2
-1@@ -960,7 +988,8 @@ struct write_commit_graph_context { report_progress:1, split:1, changed_paths:1, - order_by_pack:1; + order_by_pack:1, + write_generation_data:1;struct topo_level_slab *topo_levels;const struct commit_graph_opts *opts;
All right, this adds necessary fields to struct write_commit_graph_context.
commit-graph.c static int write_graph_chunk_data(struct hashfile *f, · line 1155
Quoted 8 lines
commit-graph.c +4
-0@@ -1120,6 +1149,44 @@ static int write_graph_chunk_data(struct hashfile *f,return 0 ; } +static int write_graph_chunk_generation_data (struct hashfile *f, +struct write_commit_graph_context *ctx) +{ +int i, num_generation_data_overflows =0 ;
Minor nitpick: in my opinion there should be empty line here, between the variables declaration and the code... however not all write_graph_chunk_*() functions have it.
commit-graph.c static int write_graph_chunk_data(struct hashfile *f, · line 1159
Quoted 4 lines
commit-graph.c +4
-0+for (i =0 ; i < ctx->commits.nr; i++) { +struct commit *c = ctx->commits.list[i]; +timestamp_t offset =commit_graph_data_at (c)->generation - c->date; +display_progress (ctx->progress, ++ctx->progress_cnt);
All right.
commit-graph.c static int write_graph_chunk_data(struct hashfile *f, · line 1164
Quoted 5 lines
commit-graph.c +5
-0+ +if (offset >GENERATION_NUMBER_V2_OFFSET_MAX ) { + offset =CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW | num_generation_data_overflows; + num_generation_data_overflows++; + }
Hmmm... shouldn't we store these commits that need overflow handling (with corrected commit date offset greater than GENERATION_NUMBER_V2_OFFSET_MAX) in a list or a queue, to remember them for writing GDOV chunk?
We could store oids, or we could store commits themselves, for example:
if (offset > GENERATION_NUMBER_V2_OFFSET_MAX) {
offset = CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW | num_generation_data_overflows;
num_generation_data_overflows++;
ALLOC_GROW(ctx->gdov_commits.list, ctx->gdov_commits.nr + 1, ctx->gdov_commits.alloc);
ctx->commits.list[ctx->gdov_commits.nr] = c;
ctx->gdov_commits.nr++;
}Though in the above proposal we could get rid of num_generation_data_overflows, as it should be the same as ctx->gdov_commits.nr.
I have called the extra commit list member of write_commit_graph_context gdov_commits, but perhaps a better name would be commits_gen_v2_overflow, or similar more descriptive name.
commit-graph.c static int write_graph_chunk_data(struct hashfile *f, · line 1170
Quoted 6 lines
commit-graph.c +6
-0+ +hashwrite_be32 (f, offset); + } + +return 0 ; +}
All right.
commit-graph.c static int write_graph_chunk_data(struct hashfile *f, · line 1176
Quoted 6 lines
commit-graph.c +6
-0+ +static int write_graph_chunk_generation_data_overflow (struct hashfile *f, +struct write_commit_graph_context *ctx) +{ +int i; +for (i =0 ; i < ctx->commits.nr; i++) {
Here we loop over *all* commits again, instead of looping over those very rare commits that need overflow handling for their corrected commit date data.
Though this possible performance issue^* could be fixed in the future commit.
*) It needs to be actually benchmarked which version is faster.
With the change proposed above (and required changes to the struct write_commit_graph_context) it could look like this:
for (i = 0; i < ctx->gcov_commits.nr; i++) {commit-graph.c static int write_graph_chunk_data(struct hashfile *f, · line 1185
Quoted 9 lines
commit-graph.c +9
-0+struct commit *c = ctx->commits.list[i]; +timestamp_t offset =commit_graph_data_at (c)->generation - c->date; +display_progress (ctx->progress, ++ctx->progress_cnt); + +if (offset >GENERATION_NUMBER_V2_OFFSET_MAX ) { +hashwrite_be32 (f, offset >>32 ); +hashwrite_be32 (f, (uint32_t ) offset); + } + }
The above would be as simple as the following:
struct commit *c = ctx->gcov_commits.list[i]; timestamp_t offset = commit_graph_data_at(c)->generation - c->date; display_progress(ctx->progress, ++ctx->progress_cnt); hashwrite_be64(f, offset);
Assumming that there would be hashwrite_be64(), it would be the following otherwise:
hashwrite_be32(f, offset >> 32); hashwrite_be32(f, (uint32_t)offset);
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1469
Quoted 12 lines
commit-graph.c +5
-0+ +return 0 ; +} +static int write_graph_chunk_extra_edges (struct hashfile *f,struct write_commit_graph_context *ctx) {@@ -1399,7 +1466,11 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx)if (current->date && current->date > max_corrected_commit_date) max_corrected_commit_date = current->date -1 ; +
This is a bit unrelated change, adding this empty line.
commit-graph.c static void compute_generation_numbers(struct write_commit_graph_context *ctx) · line 1473
Quoted 4 lines
commit-graph.c +3
-0commit_graph_data_at (current)->generation = max_corrected_commit_date +1 ; + +if (commit_graph_data_at (current)->generation - current->date >GENERATION_NUMBER_V2_OFFSET_MAX ) + ctx->num_generation_data_overflows++;
All right, we need to track number of commits that need overflow handling for generation number v2 to know what size GDOV chunk would need to be.
commit-graph.c static int write_commit_graph_file(struct write_commit_graph_context *ctx) · line 1841
Quoted 10 lines
commit-graph.c +3
-0} } }@@ -1765,6 +1836,21 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx) chunks[2 ].id =GRAPH_CHUNKID_DATA ; chunks[2 ].size = (hashsz +16 ) * ctx->commits.nr; chunks[2 ].write_fn = write_graph_chunk_data; + +if (git_env_bool (GIT_TEST_COMMIT_GRAPH_NO_GDAT ,0 )) + ctx->write_generation_data =0 ;
All right, here we handle GIT_TEST_COMMIT_GRAPH_NO_GDAT.
commit-graph.c static int write_commit_graph_file(struct write_commit_graph_context *ctx) · line 1847
Quoted 6 lines
commit-graph.c +6
-0+if (ctx->write_generation_data) { + chunks[num_chunks].id =GRAPH_CHUNKID_GENERATION_DATA ; + chunks[num_chunks].size =sizeof (uint32_t ) * ctx->commits.nr; + chunks[num_chunks].write_fn = write_graph_chunk_generation_data; + num_chunks++; + }
All right, the GDAT chunk consist of <number of commits> entries.
commit-graph.c static int write_commit_graph_file(struct write_commit_graph_context *ctx) · line 1853
Quoted 6 lines
commit-graph.c +6
-0+if (ctx->num_generation_data_overflows) { + chunks[num_chunks].id =GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW ; + chunks[num_chunks].size =sizeof (timestamp_t) * ctx->num_generation_data_overflows; + chunks[num_chunks].write_fn = write_graph_chunk_generation_data_overflow; + num_chunks++; + }
All right, that's what num_generation_data_overflows was for.
Quoted 25 lines
commit-graph.c +2
-0if (ctx->num_extra_edges) { chunks[num_chunks].id =GRAPH_CHUNKID_EXTRAEDGES ; chunks[num_chunks].size =4 * ctx->num_extra_edges;@@ -2170,6 +2256,8 @@ int write_commit_graph(struct object_directory *odb, ctx->split = flags &COMMIT_GRAPH_WRITE_SPLIT ?1 :0 ; ctx->opts = opts; ctx->total_bloom_filter_data_size =0 ; + ctx->write_generation_data =1 ; + ctx->num_generation_data_overflows =0 ; bloom_settings.bits_per_entry =git_env_ulong ("GIT_TEST_BLOOM_SETTINGS_BITS_PER_ENTRY" , bloom_settings.bits_per_entry);commit-graph.h +1
-0@@ -6,6 +6,7 @@#include "oidset.h" #define GIT_TEST_COMMIT_GRAPH "GIT_TEST_COMMIT_GRAPH" +#define GIT_TEST_COMMIT_GRAPH_NO_GDAT "GIT_TEST_COMMIT_GRAPH_NO_GDAT"#define GIT_TEST_COMMIT_GRAPH_DIE_ON_PARSE "GIT_TEST_COMMIT_GRAPH_DIE_ON_PARSE"#define GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS "GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS"
commit-graph.h struct commit_graph { · line 73
Quoted 5 lines
commit-graph.h +2
-0@@ -68,6 +69,8 @@ struct commit_graph {const uint32_t *chunk_oid_fanout;const unsigned char *chunk_oid_lookup;const unsigned char *chunk_commit_data; +const unsigned char *chunk_generation_data; +const unsigned char *chunk_generation_data_overflow;
All right, two new chunks: GDAT and GDOV.
commit.h line 17
Quoted 12 lines
commit-graph.h +0
-0const unsigned char *chunk_extra_edges;const unsigned char *chunk_base_graphs;const unsigned char *chunk_bloom_indexes;commit.h +1
-0@@ -14,6 +14,7 @@#define GENERATION_NUMBER_INFINITY ((1ULL << 63) - 1)#define GENERATION_NUMBER_V1_MAX 0x3FFFFFFF#define GENERATION_NUMBER_ZERO 0 +#define GENERATION_NUMBER_V2_OFFSET_MAX ((1ULL << 31) - 1)
Should we use this form, or hexadecimal constant?
#define GENERATION_NUMBER_V2_OFFSET_MAX 0x7FFFFFFF
But I think the current definition is more explicit: all bits set to one except for the most significant digit. All right.
Quoted 8 lines
commit.h +0
-0struct commit_list {struct commit *item;t/README +0
-0
t/README GIT_TEST_COMMIT_GRAPH=<boolean>, when true, forces the commit-graph to · line 384
Quoted 6 lines
t/README +3
-0@@ -379,6 +379,9 @@ GIT_TEST_COMMIT_GRAPH=<boolean>, when true, forces the commit-graph to be written after every 'git commit' command, and overrides the 'core.commitGraph' setting to true. +GIT_TEST_COMMIT_GRAPH_NO_GDAT=<boolean>, when true, forces the +commit-graph to be written without generation data chunk. +
All right. Nice have it documented.
Quoted 8 lines
t/README +0
-0GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=<boolean>, when true, forces commit-graph write to compute and write changed path Bloom filters for every 'git commit-graph write', as if the `--changed-paths` option wast/helper/test-read-graph.c +0
-0
t/helper/test-read-graph.c int cmd__read_graph(int argc, const char **argv) · line 42
Quoted 10 lines
t/helper/test-read-graph.c +4
-0@@ -33,6 +33,10 @@ int cmd__read_graph(int argc, const char **argv)printf (" oid_lookup" );if (graph->chunk_commit_data)printf (" commit_metadata" ); +if (graph->chunk_generation_data) +printf (" generation_data" ); +if (graph->chunk_generation_data_overflow) +printf (" generation_data_overflow" );if (graph->chunk_extra_edges)printf (" extra_edges" );if (graph->chunk_bloom_indexes)
All right, updating test-tool read-graph with new chunks.
t/t4216-log-bloom.sh test_expect_success 'setup test - repo, commits, commit graph, log outputs' ' · line 44
Quoted 11 lines
t/t4216-log-bloom.sh +1
-1@@ -40,11 +40,11 @@ test_expect_success 'setup test - repo, commits, commit graph, log outputs' ' ' graph_read_expect () { - NUM_CHUNKS=5 + NUM_CHUNKS=6 cat >expect <<- EOF
Sidenote: I have just noticed this, and as I see it is not something you wrote, but usually we write it with no space after the dash and before 'EOF':
cat >expect <<-EOF
t/t4216-log-bloom.sh test_expect_success 'setup test - repo, commits, commit graph, log outputs' ' · line 50
Quoted 7 lines
t/t4216-log-bloom.sh +1
-1header: 43475048 1 $(test_oid oid_version) $NUM_CHUNKS 0 num_commits: $1 - chunks: oid_fanout oid_lookup commit_metadata bloom_indexes bloom_data + chunks: oid_fanout oid_lookup commit_metadata generation_data bloom_indexes bloom_data EOF test-tool read-graph >actual && test_cmp expect actual
All right, updating expect value for test-tool read-graph in the usual case, with generation number chunk.
t/t5318-commit-graph.sh graph_git_behavior 'no graph' full commits/3 commits/1 · line 79
Quoted 10 lines
t/t5318-commit-graph.sh +1
-1@@ -76,7 +76,7 @@ graph_git_behavior 'no graph' full commits/3 commits/1 graph_read_expect() { OPTIONAL="" NUM_CHUNKS=3 - if test ! -z $2 + if test ! -z "$2"
All right, that is straighforward fix, which is now needed.
Quoted 84 lines
t/t5318-commit-graph.sh +9
-9then OPTIONAL=" $2" NUM_CHUNKS=$((3 + $(echo "$2" | wc -w)))@@ -103,14 +103,14 @@ test_expect_success 'exit with correct error on bad input to --stdin-commits' ' # valid commit and tree OID git rev-parse HEAD HEAD^{tree} >in && git commit-graph write --stdin-commits <in && - graph_read_expect 3 + graph_read_expect 3 generation_data ' test_expect_success 'write graph' ' cd "$TRASH_DIRECTORY/full" && git commit-graph write && test_path_is_file $objdir/info/commit-graph && - graph_read_expect "3" + graph_read_expect "3" generation_data ' test_expect_success POSIXPERM 'write graph has correct permissions' '@@ -219,7 +219,7 @@ test_expect_success 'write graph with merges' ' cd "$TRASH_DIRECTORY/full" && git commit-graph write && test_path_is_file $objdir/info/commit-graph && - graph_read_expect "10" "extra_edges" + graph_read_expect "10" "generation_data extra_edges" ' graph_git_behavior 'merge 1 vs 2' full merge/1 merge/2@@ -254,7 +254,7 @@ test_expect_success 'write graph with new commit' ' cd "$TRASH_DIRECTORY/full" && git commit-graph write && test_path_is_file $objdir/info/commit-graph && - graph_read_expect "11" "extra_edges" + graph_read_expect "11" "generation_data extra_edges" ' graph_git_behavior 'full graph, commit 8 vs merge 1' full commits/8 merge/1@@ -264,7 +264,7 @@ test_expect_success 'write graph with nothing new' ' cd "$TRASH_DIRECTORY/full" && git commit-graph write && test_path_is_file $objdir/info/commit-graph && - graph_read_expect "11" "extra_edges" + graph_read_expect "11" "generation_data extra_edges" ' graph_git_behavior 'cleared graph, commit 8 vs merge 1' full commits/8 merge/1@@ -274,7 +274,7 @@ test_expect_success 'build graph from latest pack with closure' ' cd "$TRASH_DIRECTORY/full" && cat new-idx | git commit-graph write --stdin-packs && test_path_is_file $objdir/info/commit-graph && - graph_read_expect "9" "extra_edges" + graph_read_expect "9" "generation_data extra_edges" ' graph_git_behavior 'graph from pack, commit 8 vs merge 1' full commits/8 merge/1@@ -287,7 +287,7 @@ test_expect_success 'build graph from commits with closure' ' git rev-parse merge/1 >>commits-in && cat commits-in | git commit-graph write --stdin-commits && test_path_is_file $objdir/info/commit-graph && - graph_read_expect "6" + graph_read_expect "6" "generation_data" ' graph_git_behavior 'graph from commits, commit 8 vs merge 1' full commits/8 merge/1@@ -297,7 +297,7 @@ test_expect_success 'build graph from commits with append' ' cd "$TRASH_DIRECTORY/full" && git rev-parse merge/3 | git commit-graph write --stdin-commits --append && test_path_is_file $objdir/info/commit-graph && - graph_read_expect "10" "extra_edges" + graph_read_expect "10" "generation_data extra_edges" ' graph_git_behavior 'append graph, commit 8 vs merge 1' full commits/8 merge/1@@ -307,7 +307,7 @@ test_expect_success 'build graph using --reachable' ' cd "$TRASH_DIRECTORY/full" && git commit-graph write --reachable && test_path_is_file $objdir/info/commit-graph && - graph_read_expect "11" "extra_edges" + graph_read_expect "11" "generation_data extra_edges" ' graph_git_behavior 'append graph, commit 8 vs merge 1' full commits/8 merge/1
t/t5318-commit-graph.sh test_expect_success 'write graph in bare repo' ' · line 332
Quoted 6 lines
t/t5318-commit-graph.sh +1
-1@@ -328,7 +328,7 @@ test_expect_success 'write graph in bare repo' ' cd "$TRASH_DIRECTORY/bare" && git commit-graph write && test_path_is_file $baredir/info/commit-graph && - graph_read_expect "11" "extra_edges" + graph_read_expect "11" "generation_data extra_edges" '
All those just add "generation_data" (aka GDAT) to expected chunks. All right.
t/t5318-commit-graph.sh test_expect_success 'warn on improper hash version' ' · line 458
Quoted 10 lines
t/t5318-commit-graph.sh +2
-2graph_git_behavior 'bare repo with graph, commit 8 vs merge 1' bare commits/8 merge/1@@ -454,8 +454,9 @@ test_expect_success 'warn on improper hash version' ' test_expect_success 'git commit-graph verify' ' cd "$TRASH_DIRECTORY/full" && - git rev-parse commits/8 | git commit-graph write --stdin-commits && - git commit-graph verify >output + git rev-parse commits/8 | GIT_TEST_COMMIT_GRAPH_NO_GDAT=1 git commit-graph write --stdin-commits && + git commit-graph verify >output &&
All right, this simply adds GIT_TEST_COMMIT_GRAPH_NO_GDAT=1. I assume this is needed because this test is also setup for the following commits _without_ even saying that in the test name (bad practice, in my opinion), and the comment above this test says the following:
# the verify tests below expect the commit-graph to contain # exactly the commits reachable from the commits/8 branch. # If the file changes the set of commits in the list, then the # offsets into the binary file will result in different edits # and the tests will likely break.
So the following tests are fragile (though perhaps unavoidably fragile), and without this change they would not work, I assume.
t/t5318-commit-graph.sh test_expect_success 'warn on improper hash version' ' · line 459
Quoted 1 line
t/t5318-commit-graph.sh +1
-0+ graph_read_expect 9 extra_edges
I guess that this is here to check that GIT_TEST_COMMIT_GRAPH_NO_GDAT=1 work as intended, and that the following "verify" tests wouldn't break. I understand its necessity, even if I don't quite like having a test that checks multiple things. This is a minor issue, though.
All right.
We might want to have a separate test that checks that we get commit-graph with and without GDAT chunk depending on whether we use GIT_TEST_COMMIT_GRAPH_NO_GDAT=1. On the other hand, this environment variable is there purely for tests, so the question is should we test the test infrastructure?
Quoted 4 lines
t/t5318-commit-graph.sh +0
-0' NUM_COMMITS=9
t/t5318-commit-graph.sh test_expect_success 'corrupt commit-graph write (missing tree)' ' · line 751
Quoted 10 lines
t/t5318-commit-graph.sh +7
-0@@ -741,4 +742,47 @@ test_expect_success 'corrupt commit-graph write (missing tree)' ' ) ' +test_commit_with_date() { + file="$1.t" && + echo "$1" >"$file" && + git add "$file" && + GIT_COMMITTER_DATE="$2" GIT_AUTHOR_DATE="$2" git commit -m "$1" + git tag "$1" +}
Here we add a helper function. All right.
I wonder though if it wouldn't be a better idea to add --date <date> option to the test_commit() function in test-lib-functions.sh (which option would set GIT_COMMITTER_DATE and GIT_AUTHOR_DATE, and also set notick=yes).
For example:
t/test-lib-functions.sh +6 -0
@@ -202,6 +202,12 @@ test_commit () { --signoff) signoff="$1" ;; + --date) + notick=yes + GIT_COMMITTER_DATE="$2" + GIT_AUTHOR_DATE="$2" + shift + ;; -C) indir="$2" shift
t/t5318-commit-graph.sh test_expect_success 'corrupt commit-graph write (missing tree)' ' · line 752
Quoted 1 line
t/t5318-commit-graph.sh +1
-0+
It would be nice to have there comment describing the shape of the revision history we generate here, that currenly is present only in the commmit message.
# We test the overflow-related code with the following repo history:
#
# 4:F - 5:N - 6:U
# / \
# 1:U - 2:N - 3:U M:N
# \ /
# 7:N - 8:F - 9:N
#
# Here the commits denoted by U have committer date of zero seconds
# since Unix epoch, the commits denoted by N have committer date # starting from 1112354055 seconds since Unix epoch (default committer # date for the test suite), and the commits denoted by F have committer # date of (2 ^ 31 - 2) seconds since Unix epoch. # # The largest offset observed is 2 ^ 31, just large enough to overflow. #
t/t5318-commit-graph.sh test_expect_success 'corrupt commit-graph write (missing tree)' ' · line 756
Quoted 4 lines
t/t5318-commit-graph.sh +4
-0+test_expect_success 'overflow corrected commit date offset' ' + objdir=".git/objects" && + UNIX_EPOCH_ZERO="1970-01-01 00:00 +0000" && + FUTURE_DATE="@2147483646 +0000" &&
It is a bit funny to see UNIX_EPOCH_ZERO spelled one way, and FUTURE_DATE other way.
Wouldn't be more readable to use UNIX_EPOCH_ZERO="@0 +0000"?
Quoted 14 lines
t/t5318-commit-graph.sh +14
-0+ test_oid_cache <<-EOF && + oid_version sha1:1 + oid_version sha256:2 + EOF + cd "$TRASH_DIRECTORY" && + mkdir repo && + cd repo && + git init && + test_commit_with_date 1 "$UNIX_EPOCH_ZERO" && + test_commit 2 && + test_commit_with_date 3 "$UNIX_EPOCH_ZERO" && + git commit-graph write --reachable && + graph_read_expect 3 generation_data && + test_commit_with_date 4 "$FUTURE_DATE" &&
t/t5318-commit-graph.sh test_expect_success 'corrupt commit-graph write (missing tree)' ' · line 780
Quoted 10 lines
t/t5318-commit-graph.sh +10
-0+ test_commit 5 && + test_commit_with_date 6 "$UNIX_EPOCH_ZERO" && + git branch left && + git reset --hard 3 && + test_commit 7 && + test_commit_with_date 8 "$FUTURE_DATE" && + test_commit 9 && + git branch right && + git reset --hard 3 && + git merge left right &&
We have test_merge() function in test-lib-functions.sh, perhaps we should use it here.
t/t5318-commit-graph.sh test_expect_success 'corrupt commit-graph write (missing tree)' ' · line 782
Quoted 2 lines
t/t5318-commit-graph.sh +2
-0+ git commit-graph write --reachable && + graph_read_expect 10 "generation_data generation_data_overflow" &&
All right, we write the commit-graph and check that it has both GDAT and GDOV chunks present.
t/t5318-commit-graph.sh test_expect_success 'corrupt commit-graph write (missing tree)' ' · line 783
Quoted 1 line
t/t5318-commit-graph.sh +1
-0+ git commit-graph verify
All right, we checks that created commit graph with GDAT and GDOV passes 'git commit-graph verify` checks.
t/t5318-commit-graph.sh test_expect_success 'corrupt commit-graph write (missing tree)' ' · line 786
Quoted 3 lines
t/t5318-commit-graph.sh +3
-0+' + +graph_git_behavior 'overflow corrected commit date offset' repo left right
All right, here we compare the Git behavior with the commit-graph to the behavior without it... however I think that those two tests really should have distinct (different) test names. Currently they both use 'overflow corrected commit date offset'.
Quoted 24 lines
t/t5318-commit-graph.sh +1
-0+ test_donet/t5324-split-commit-graph.sh +4
-4@@ -13,11 +13,11 @@ test_expect_success 'setup repo' ' infodir=".git/objects/info" && graphdir="$infodir/commit-graphs" && test_oid_cache <<-EOM - shallow sha1:1760 - shallow sha256:2064 + shallow sha1:2132 + shallow sha256:2436 - base sha1:1376 - base sha256:1496 + base sha1:1408 + base sha256:1528 oid_version sha1:1 oid_version sha256:2@@ -31,9 +31,9 @@ graph_read_expect() { NUM_BASE=$2
t/t5324-split-commit-graph.sh graph_read_expect() { · line 39
Quoted 10 lines
t/t5324-split-commit-graph.sh +2
-2fi cat >expect <<- EOF - header: 43475048 1 $(test_oid oid_version) 3 $NUM_BASE + header: 43475048 1 $(test_oid oid_version) 4 $NUM_BASE num_commits: $1 - chunks: oid_fanout oid_lookup commit_metadata + chunks: oid_fanout oid_lookup commit_metadata generation_data EOF test-tool read-graph >output && test_cmp expect output
All right, we now expect the commit graph to include the GDAT chunk... though shouldn't be there old expected value for no GDAT, for future tests? But perhaps this is not necessary.
Note that I have not checked the details, but it looks OK to me.
t/t6600-test-reach.sh test_expect_success 'setup' ' · line 60
Quoted 11 lines
t/t6600-test-reach.sh +3
-0@@ -55,10 +55,13 @@ test_expect_success 'setup' ' git show-ref -s commit-5-5 | git commit-graph write --stdin-commits && mv .git/objects/info/commit-graph commit-graph-half && chmod u+w commit-graph-half && + GIT_TEST_COMMIT_GRAPH_NO_GDAT=1 git commit-graph write --reachable && + mv .git/objects/info/commit-graph commit-graph-no-gdat && + chmod u+w commit-graph-no-gdat &&
All right, this prepares for testing one more mode. The run_all_modes() function would test the following cases:
- no commit-graph
- commit-graph for all commits, with GDAT
- commit-graph with half of commits, with GDAT
- commit-graph for all commits, without GDAT
Quoted 13 lines
t/t6600-test-reach.sh +2
-1git config core.commitGraph true ' -run_three_modes () { +run_all_modes () { test_when_finished rm -rf .git/objects/info/commit-graph && "$@" <input >actual && test_cmp expect actual &&@@ -67,11 +70,14 @@ run_three_modes () { test_cmp expect actual && cp commit-graph-half .git/objects/info/commit-graph && "$@" <input >actual && + test_cmp expect actual &&
t/t6600-test-reach.sh run_three_modes () { · line 81
Quoted 10 lines
t/t6600-test-reach.sh +4
-2+ cp commit-graph-no-gdat .git/objects/info/commit-graph && + "$@" <input >actual && test_cmp expect actual } -test_three_modes () { - run_three_modes test-tool reach "$@" +test_all_modes () { + run_all_modes test-tool reach "$@" }
All right.
Though to reduce "noise" in this patch, the rename of run_three_modes() to run_all_modes() and test_three_modes() to test_all_modes() could have been done in a separate preparatory patch. It would be pure refactoring patch, without introducing any new functionality.
Quoted 232 lines
t/t6600-test-reach.sh +27
-27test_expect_success 'ref_newer:miss' '@@ -80,7 +86,7 @@ test_expect_success 'ref_newer:miss' ' B:commit-4-9 EOF echo "ref_newer(A,B):0" >expect && - test_three_modes ref_newer + test_all_modes ref_newer ' test_expect_success 'ref_newer:hit' '@@ -89,7 +95,7 @@ test_expect_success 'ref_newer:hit' ' B:commit-2-3 EOF echo "ref_newer(A,B):1" >expect && - test_three_modes ref_newer + test_all_modes ref_newer ' test_expect_success 'in_merge_bases:hit' '@@ -98,7 +104,7 @@ test_expect_success 'in_merge_bases:hit' ' B:commit-8-8 EOF echo "in_merge_bases(A,B):1" >expect && - test_three_modes in_merge_bases + test_all_modes in_merge_bases ' test_expect_success 'in_merge_bases:miss' '@@ -107,7 +113,7 @@ test_expect_success 'in_merge_bases:miss' ' B:commit-5-9 EOF echo "in_merge_bases(A,B):0" >expect && - test_three_modes in_merge_bases + test_all_modes in_merge_bases ' test_expect_success 'in_merge_bases_many:hit' '@@ -117,7 +123,7 @@ test_expect_success 'in_merge_bases_many:hit' ' X:commit-5-7 EOF echo "in_merge_bases_many(A,X):1" >expect && - test_three_modes in_merge_bases_many + test_all_modes in_merge_bases_many ' test_expect_success 'in_merge_bases_many:miss' '@@ -127,7 +133,7 @@ test_expect_success 'in_merge_bases_many:miss' ' X:commit-8-6 EOF echo "in_merge_bases_many(A,X):0" >expect && - test_three_modes in_merge_bases_many + test_all_modes in_merge_bases_many ' test_expect_success 'in_merge_bases_many:miss-heuristic' '@@ -137,7 +143,7 @@ test_expect_success 'in_merge_bases_many:miss-heuristic' ' X:commit-6-6 EOF echo "in_merge_bases_many(A,X):0" >expect && - test_three_modes in_merge_bases_many + test_all_modes in_merge_bases_many ' test_expect_success 'is_descendant_of:hit' '@@ -148,7 +154,7 @@ test_expect_success 'is_descendant_of:hit' ' X:commit-1-1 EOF echo "is_descendant_of(A,X):1" >expect && - test_three_modes is_descendant_of + test_all_modes is_descendant_of ' test_expect_success 'is_descendant_of:miss' '@@ -159,7 +165,7 @@ test_expect_success 'is_descendant_of:miss' ' X:commit-7-6 EOF echo "is_descendant_of(A,X):0" >expect && - test_three_modes is_descendant_of + test_all_modes is_descendant_of ' test_expect_success 'get_merge_bases_many' '@@ -174,7 +180,7 @@ test_expect_success 'get_merge_bases_many' ' git rev-parse commit-5-6 \ commit-4-7 | sort } >expect && - test_three_modes get_merge_bases_many + test_all_modes get_merge_bases_many ' test_expect_success 'reduce_heads' '@@ -196,7 +202,7 @@ test_expect_success 'reduce_heads' ' commit-2-8 \ commit-1-10 | sort } >expect && - test_three_modes reduce_heads + test_all_modes reduce_heads ' test_expect_success 'can_all_from_reach:hit' '@@ -219,7 +225,7 @@ test_expect_success 'can_all_from_reach:hit' ' Y:commit-8-1 EOF echo "can_all_from_reach(X,Y):1" >expect && - test_three_modes can_all_from_reach + test_all_modes can_all_from_reach ' test_expect_success 'can_all_from_reach:miss' '@@ -241,7 +247,7 @@ test_expect_success 'can_all_from_reach:miss' ' Y:commit-8-5 EOF echo "can_all_from_reach(X,Y):0" >expect && - test_three_modes can_all_from_reach + test_all_modes can_all_from_reach ' test_expect_success 'can_all_from_reach_with_flag: tags case' '@@ -264,7 +270,7 @@ test_expect_success 'can_all_from_reach_with_flag: tags case' ' Y:commit-8-1 EOF echo "can_all_from_reach_with_flag(X,_,_,0,0):1" >expect && - test_three_modes can_all_from_reach_with_flag + test_all_modes can_all_from_reach_with_flag ' test_expect_success 'commit_contains:hit' '@@ -280,8 +286,8 @@ test_expect_success 'commit_contains:hit' ' X:commit-9-3 EOF echo "commit_contains(_,A,X,_):1" >expect && - test_three_modes commit_contains && - test_three_modes commit_contains --tag + test_all_modes commit_contains && + test_all_modes commit_contains --tag ' test_expect_success 'commit_contains:miss' '@@ -297,8 +303,8 @@ test_expect_success 'commit_contains:miss' ' X:commit-9-3 EOF echo "commit_contains(_,A,X,_):0" >expect && - test_three_modes commit_contains && - test_three_modes commit_contains --tag + test_all_modes commit_contains && + test_all_modes commit_contains --tag ' test_expect_success 'rev-list: basic topo-order' '@@ -310,7 +316,7 @@ test_expect_success 'rev-list: basic topo-order' ' commit-6-2 commit-5-2 commit-4-2 commit-3-2 commit-2-2 commit-1-2 \ commit-6-1 commit-5-1 commit-4-1 commit-3-1 commit-2-1 commit-1-1 \ >expect && - run_three_modes git rev-list --topo-order commit-6-6 + run_all_modes git rev-list --topo-order commit-6-6 ' test_expect_success 'rev-list: first-parent topo-order' '@@ -322,7 +328,7 @@ test_expect_success 'rev-list: first-parent topo-order' ' commit-6-2 \ commit-6-1 commit-5-1 commit-4-1 commit-3-1 commit-2-1 commit-1-1 \ >expect && - run_three_modes git rev-list --first-parent --topo-order commit-6-6 + run_all_modes git rev-list --first-parent --topo-order commit-6-6 ' test_expect_success 'rev-list: range topo-order' '@@ -334,7 +340,7 @@ test_expect_success 'rev-list: range topo-order' ' commit-6-2 commit-5-2 commit-4-2 \ commit-6-1 commit-5-1 commit-4-1 \ >expect && - run_three_modes git rev-list --topo-order commit-3-3..commit-6-6 + run_all_modes git rev-list --topo-order commit-3-3..commit-6-6 ' test_expect_success 'rev-list: range topo-order' '@@ -346,7 +352,7 @@ test_expect_success 'rev-list: range topo-order' ' commit-6-2 commit-5-2 commit-4-2 \ commit-6-1 commit-5-1 commit-4-1 \ >expect && - run_three_modes git rev-list --topo-order commit-3-8..commit-6-6 + run_all_modes git rev-list --topo-order commit-3-8..commit-6-6 ' test_expect_success 'rev-list: first-parent range topo-order' '@@ -358,7 +364,7 @@ test_expect_success 'rev-list: first-parent range topo-order' ' commit-6-2 \ commit-6-1 commit-5-1 commit-4-1 \ >expect && - run_three_modes git rev-list --first-parent --topo-order commit-3-8..commit-6-6 + run_all_modes git rev-list --first-parent --topo-order commit-3-8..commit-6-6 ' test_expect_success 'rev-list: ancestry-path topo-order' '@@ -368,7 +374,7 @@ test_expect_success 'rev-list: ancestry-path topo-order' ' commit-6-4 commit-5-4 commit-4-4 commit-3-4 \ commit-6-3 commit-5-3 commit-4-3 \ >expect && - run_three_modes git rev-list --topo-order --ancestry-path commit-3-3..commit-6-6 + run_all_modes git rev-list --topo-order --ancestry-path commit-3-3..commit-6-6 ' test_expect_success 'rev-list: symmetric difference topo-order' '@@ -382,7 +388,7 @@ test_expect_success 'rev-list: symmetric difference topo-order' ' commit-3-8 commit-2-8 commit-1-8 \ commit-3-7 commit-2-7 commit-1-7 \ >expect && - run_three_modes git rev-list --topo-order commit-3-8...commit-6-6 + run_all_modes git rev-list --topo-order commit-3-8...commit-6-6 ' test_expect_success 'get_reachable_subset:all' '@@ -402,7 +408,7 @@ test_expect_success 'get_reachable_subset:all' ' commit-1-7 \ commit-5-6 | sort ) >expect && - test_three_modes get_reachable_subset + test_all_modes get_reachable_subset ' test_expect_success 'get_reachable_subset:some' '@@ -420,7 +426,7 @@ test_expect_success 'get_reachable_subset:some' ' git rev-parse commit-3-3 \ commit-1-7 | sort ) >expect && - test_three_modes get_reachable_subset + test_all_modes get_reachable_subset ' test_expect_success 'get_reachable_subset:none' '
t/t6600-test-reach.sh test_expect_success 'get_reachable_subset:none' ' · line 443
Quoted 5 lines
t/t6600-test-reach.sh +1
-1@@ -434,7 +440,7 @@ test_expect_success 'get_reachable_subset:none' ' Y:commit-2-8 EOF echo "get_reachable_subset(X,Y)" >expect && - test_three_modes get_reachable_subset + test_all_modes get_reachable_subset
All those are pure renames of test_three_modes() to test_all_modes(), which now does tests for one more mode -- without GDAT.
All right.
t/t6600-test-reach.sh test_expect_success 'get_reachable_subset:none' ' · line 446
Quoted 3 lines
t/t6600-test-reach.sh +0
-0' test_done
Best,
Signature · 1 line
Jakub Narębski
Re: [PATCH v4 07/10] commit-graph: implement generation data chunk
Quoted 16 lines · Jakub Narębski
...
Quoted 5 lines
While storing corrected commit date offset instead of the corrected commit date saves us 4 bytes per commit, it's possible for the offsets to overflow the 4-bytes allocated. As such overflows are exceedingly rare, we use the following overflow management scheme:
Perhaps it would be good idea to write the idea in full from start, as the commit message is intended to be read stadalone and not in the context of the patch series. On the other hand it might be too much detail in already [necessarily] lengthty commit message.
Perhaps something like the following proposal would read better.
Quoted 8 lines
To minimize the space required to store corrected commit date, Git stores corrected commit date offsets into the commit-graph file, instead of corrected commit dates themselves. This saves us 4 bytes per commit, decreasing the GDAT chunk size by half, but it's possible for the offset to overflow the 4-bytes allocated for storage. As such overflows are and should be exceedingly rare, we use the following overflow management scheme:
Thanks, that's better.
Quoted 5 lines
...
Quoted 2 lines
We test the overflow-related code with the following repo history:
Quoted 9 lines
Quoted 5 lines
F - N - U
/ \
U - N - U N
\ /
N - F - NDo we need such complex history? I guess we need to test the handling of merge commits too.
I wanted to test three cases - a root epoch zero commit, a commit that's far enough in past to overflow the offset and a commit that's far enough in the future to overflow the offset.
Quoted 184 lines
Quoted 30 lines
Where the commits denoted by U have committer date of zero seconds since Unix epoch, the commits denoted by N have committer date of 1112354055 (default committer date for the test suite) seconds since Unix epoch and the commits denoted by F have committer date of (2 ^ 31 - 2) seconds since Unix epoch.
The largest offset observed is 2 ^ 31, just large enough to overflow.
[1]: https://lore.kernel.org/git/[email protected]/
Signed-off-by Abhishek Kumar <[email protected]>--- commit-graph.c | 98 +++++++++++++++++++++++++++++++++-- commit-graph.h | 3 ++ commit.h | 1 + t/README | 3 ++ t/helper/test-read-graph.c | 4 ++ t/t4216-log-bloom.sh | 4 +- t/t5318-commit-graph.sh | 70 ++++++++++++++++++++----- t/t5324-split-commit-graph.sh | 12 ++--- t/t6600-test-reach.sh | 68 +++++++++++++----------- 9 files changed, 206 insertions(+), 57 deletions(-)commit-graph.c +0
-0@@ -38,11 +38,13 @@ void git_test_write_commit_graph_or_die(void)#define GRAPH_CHUNKID_OIDFANOUT 0x4f494446/* "OIDF" */ commit-graph.c void git_test_write_commit_graph_or_die(void) · line 47
Quoted 10 lines
commit-graph.c +3
-1#define GRAPH_CHUNKID_OIDLOOKUP 0x4f49444c/* "OIDL" */ #define GRAPH_CHUNKID_DATA 0x43444154/* "CDAT" */ +#define GRAPH_CHUNKID_GENERATION_DATA 0x47444154/* "GDAT" */ +#define GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW 0x47444f56/* "GDOV" */ #define GRAPH_CHUNKID_EXTRAEDGES 0x45444745/* "EDGE" */ #define GRAPH_CHUNKID_BLOOMINDEXES 0x42494458/* "BIDX" */ #define GRAPH_CHUNKID_BLOOMDATA 0x42444154/* "BDAT" */ #define GRAPH_CHUNKID_BASE 0x42415345/* "BASE" */ -#define MAX_NUM_CHUNKS 7 +#define MAX_NUM_CHUNKS 9All right.
commit-graph.c void git_test_write_commit_graph_or_die(void) · line 67
Quoted 9 lines
commit-graph.c +2
-0#define GRAPH_DATA_WIDTH (the_hash_algo->rawsz + 16)@@ -61,6 +63,8 @@ void git_test_write_commit_graph_or_die(void)#define GRAPH_MIN_SIZE (GRAPH_HEADER_SIZE + 4 * GRAPH_CHUNKLOOKUP_WIDTH \ + GRAPH_FANOUT_SIZE + the_hash_algo->rawsz) +#define CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW (1ULL << 31) +All right, though the naming convention is different from the one used for EDGE chunk:
GRAPH_EXTRA_EDGES_NEEDEDandGRAPH_EDGE_LAST_MASK.Quoted 11 lines
commit-graph.c +4
-0/* Remember to update object flag allocation in object.h */ #define REACHABLE (1u<<15)@@ -385,6 +389,20 @@ struct commit_graph *parse_commit_graph(struct repository *r, graph->chunk_commit_data = data + chunk_offset;break ; +case GRAPH_CHUNKID_GENERATION_DATA : +if (graph->chunk_generation_data) + chunk_repeated =1 ; +else commit-graph.c struct commit_graph *parse_commit_graph(struct repository *r, · line 405
Quoted 10 lines
commit-graph.c +10
-0+ graph->chunk_generation_data = data + chunk_offset; +break ; + +case GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW : +if (graph->chunk_generation_data_overflow) + chunk_repeated =1 ; +else + graph->chunk_generation_data_overflow = data + chunk_offset; +break ; +Necessary but unavoidable boilerplate for adding new chunks to the commit-graph file format. All right.
commit-graph.c static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, · line 767
Quoted 11 lines
commit-graph.c +2
-2case GRAPH_CHUNKID_EXTRAEDGES :if (graph->chunk_extra_edges) chunk_repeated =1 ;@@ -745,8 +763,8 @@ static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, {const unsigned char *commit_data;struct commit_graph_data *graph_data; -uint32_t lex_index; -uint64_t date_high, date_low; +uint32_t lex_index, offset_pos; +uint64_t date_high, date_low, offset;All right, we are adding two new variables:
offsetto read data stored in GDAT chunk, andoffset_posto help read data from GDOV chunk if necessary i.e. to handle overflow in corrected commit data offset storage.commit-graph.c static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, · line 786
Quoted 10 lines
commit-graph.c +2
-1while (pos < g->num_commits_in_base) g = g->base_graph;@@ -764,7 +782,16 @@ static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, date_low =get_be32 (commit_data + g->hash_len +12 ); item->date = (timestamp_t)((date_high <<32 ) | date_low); - graph_data->generation =get_be32 (commit_data + g->hash_len +8 ) >>2 ; +if (g->chunk_generation_data) { + offset = (timestamp_t )get_be32 (g->chunk_generation_data +sizeof (uint32_t ) * lex_index);Style: why space after the
(timestamp_t)cast operator?Though CodingGuidelines do not say anything on this topic... perhaps the space after cast operator makes it more readable?
commit-graph.c static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, · line 788
Quoted 2 lines
commit-graph.c +2
-0+ +if (offset &CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW ) {All right, so the
CORRECTED_COMMIT_DATE_OFFSET_OVERFLOWis equivalent ofGRAPH_EXTRA_EDGES_NEEDED.commit-graph.c static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, · line 789
Quoted 1 line
commit-graph.c +1
-0+ offset_pos = offset ^CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW ;Hmmm... instead of using bitwise and on an equivalent to the
GRAPH_EDGE_LAST_MASK, we utilize the fact that we know that the MSB bit is set, so we can clear it with bitwise xor. Clever trick.commit-graph.c static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, · line 792
Quoted 3 lines
commit-graph.c +3
-0+ graph_data->generation =get_be64 (g->chunk_generation_data_overflow +8 * offset_pos); + }else + graph_data->generation = item->date + offset;All right, this handles the case when we have generation number v2, with or without overflow.
commit-graph.c static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, · line 794
Quoted 2 lines
commit-graph.c +2
-0+ }else + graph_data->generation =get_be32 (commit_data + g->hash_len +8 ) >>2 ;All right, this handles the case where we have only generation number v1, like for commit-graph file written by old Git.
Quoted 12 lines
commit-graph.c +1
-0if (g->topo_levels) *topo_level_slab_at (g->topo_levels, item) =get_be32 (commit_data + g->hash_len +8 ) >>2 ;@@ -942,6 +969,7 @@ struct write_commit_graph_context {struct packed_oid_list oids;struct packed_commit_list commits;int num_extra_edges; +int num_generation_data_overflows;unsigned long approx_nr_objects;struct progress *progress;int progress_done;commit-graph.c struct write_commit_graph_context { · line 995
Quoted 9 lines
commit-graph.c +2
-1@@ -960,7 +988,8 @@ struct write_commit_graph_context { report_progress:1, split:1, changed_paths:1, - order_by_pack:1; + order_by_pack:1, + write_generation_data:1;struct topo_level_slab *topo_levels;const struct commit_graph_opts *opts;All right, this adds necessary fields to
struct write_commit_graph_context.commit-graph.c static int write_graph_chunk_data(struct hashfile *f, · line 1155
Quoted 8 lines
commit-graph.c +4
-0@@ -1120,6 +1149,44 @@ static int write_graph_chunk_data(struct hashfile *f,return 0 ; } +static int write_graph_chunk_generation_data (struct hashfile *f, +struct write_commit_graph_context *ctx) +{ +int i, num_generation_data_overflows =0 ;Minor nitpick: in my opinion there should be empty line here, between the variables declaration and the code... however not all write_graph_chunk_*() functions have it.
commit-graph.c static int write_graph_chunk_data(struct hashfile *f, · line 1159
Quoted 4 lines
commit-graph.c +4
-0+for (i =0 ; i < ctx->commits.nr; i++) { +struct commit *c = ctx->commits.list[i]; +timestamp_t offset =commit_graph_data_at (c)->generation - c->date; +display_progress (ctx->progress, ++ctx->progress_cnt);All right.
Quoted 10 lines
commit-graph.c static int write_graph_chunk_data(struct hashfile *f, · line 1164
Quoted 5 lines
commit-graph.c +5
-0+ +if (offset >GENERATION_NUMBER_V2_OFFSET_MAX ) { + offset =CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW | num_generation_data_overflows; + num_generation_data_overflows++; + }Hmmm... shouldn't we store these commits that need overflow handling (with corrected commit date offset greater than
GENERATION_NUMBER_V2_OFFSET_MAX) in a list or a queue, to remember them for writing GDOV chunk?
We could, although write_graph_chunk_extra_edges() (just like this function) prefers to iterate over all commits again. Both octopus merges and overflowing corrected commit dates are exceedingly rare, might be worthwhile to trade some memory to avoid looping again.
Quoted 104 lines
We could store oids, or we could store commits themselves, for example:
if (offset > GENERATION_NUMBER_V2_OFFSET_MAX) { offset = CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW | num_generation_data_overflows; num_generation_data_overflows++; ALLOC_GROW(ctx->gdov_commits.list, ctx->gdov_commits.nr + 1, ctx->gdov_commits.alloc); ctx->commits.list[ctx->gdov_commits.nr] = c; ctx->gdov_commits.nr++; }Though in the above proposal we could get rid of
num_generation_data_overflows, as it should be the same asctx->gdov_commits.nr.I have called the extra commit list member of
write_commit_graph_contextgdov_commits, but perhaps a better name would becommits_gen_v2_overflow, or similar more descriptive name.commit-graph.c static int write_graph_chunk_data(struct hashfile *f, · line 1170
Quoted 6 lines
commit-graph.c +6
-0+ +hashwrite_be32 (f, offset); + } + +return 0 ; +}All right.
commit-graph.c static int write_graph_chunk_data(struct hashfile *f, · line 1176
Quoted 6 lines
commit-graph.c +6
-0+ +static int write_graph_chunk_generation_data_overflow (struct hashfile *f, +struct write_commit_graph_context *ctx) +{ +int i; +for (i =0 ; i < ctx->commits.nr; i++) {Here we loop over *all* commits again, instead of looping over those very rare commits that need overflow handling for their corrected commit date data.
Though this possible performance issue^* could be fixed in the future commit.
*) It needs to be actually benchmarked which version is faster.
...
commit-graph.c test_expect_success 'warn on improper hash version' ' · line 458
Quoted 10 lines
commit-graph.c +2
-2graph_git_behavior 'bare repo with graph, commit 8 vs merge 1' bare commits/8 merge/1@@ -454,8 +454,9 @@ test_expect_success 'warn on improper hash version' ' test_expect_success 'git commit-graph verify' ' cd "$TRASH_DIRECTORY/full" && - git rev-parse commits/8 | git commit-graph write --stdin-commits && - git commit-graph verify >output + git rev-parse commits/8 | GIT_TEST_COMMIT_GRAPH_NO_GDAT=1 git commit-graph write --stdin-commits && + git commit-graph verify >output &&All right, this simply adds GIT_TEST_COMMIT_GRAPH_NO_GDAT=1. I assume this is needed because this test is also setup for the following commits _without_ even saying that in the test name (bad practice, in my opinion), and the comment above this test says the following:
# the verify tests below expect the commit-graph to contain # exactly the commits reachable from the commits/8 branch. # If the file changes the set of commits in the list, then the # offsets into the binary file will result in different edits # and the tests will likely break.So the following tests are fragile (though perhaps unavoidably fragile), and without this change they would not work, I assume.
commit-graph.c test_expect_success 'warn on improper hash version' ' · line 459
Quoted 1 line
commit-graph.c +1
-0+ graph_read_expect 9 extra_edgesI guess that this is here to check that GIT_TEST_COMMIT_GRAPH_NO_GDAT=1 work as intended, and that the following "verify" tests wouldn't break. I understand its necessity, even if I don't quite like having a test that checks multiple things. This is a minor issue, though.
All right.
We might want to have a separate test that checks that we get commit-graph with and without GDAT chunk depending on whether we use GIT_TEST_COMMIT_GRAPH_NO_GDAT=1. On the other hand, this environment variable is there purely for tests, so the question is should we test the test infrastructure?
Quoted 4 lines
commit-graph.c +0
-0'NUM_COMMITS =9 commit-graph.c test_expect_success 'corrupt commit-graph write (missing tree)' ' · line 751
Quoted 10 lines
commit-graph.c +7
-0@@ -741,4 +742,47 @@ test_expect_success 'corrupt commit-graph write (missing tree)' ' ) ' +test_commit_with_date() { + file="$1.t" && + echo"$1" >"$file" && + git add "$file" && + GIT_COMMITTER_DATE="$2" GIT_AUTHOR_DATE="$2" git commit -m "$1" + git tag "$1" +}Here we add a helper function. All right.
Quoted 5 lines
I wonder though if it wouldn't be a better idea to add
--date <date>option to thetest_commit()function intest-lib-functions.sh(which option would setGIT_COMMITTER_DATEandGIT_AUTHOR_DATE, and also set notick=yes).
Yes, that's a better idea - I didn't know how to change test_commit() well enough to tinker with what's working.
Quoted 36 lines
For example:
t/test-lib-functions.sh +6
-0@@ -202,6 +202,12 @@ test_commit () { --signoff) signoff="$1" ;; + --date) + notick=yes + GIT_COMMITTER_DATE="$2" + GIT_AUTHOR_DATE="$2" + shift + ;; -C) indir="$2" shiftcommit-graph.c test_expect_success 'corrupt commit-graph write (missing tree)' ' · line 752
Quoted 3 lines
commit-graph.c +1
-0+It would be nice to have there comment describing the shape of the revision history we generate here, that currenly is present only in the commmit message.
# We test the overflow-related code with the following repo history:
#
# 4:F - 5:N - 6:U
# / \
# 1:U - 2:N - 3:U M:N
# \ /
# 7:N - 8:F - 9:N
#
# Here the commits denoted by U have committer date of zero seconds
Quoted 7 lines
# since Unix epoch, the commits denoted by N have committer date # starting from 1112354055 seconds since Unix epoch (default committer # date for the test suite), and the commits denoted by F have committer # date of (2 ^ 31 - 2) seconds since Unix epoch. # # The largest offset observed is 2 ^ 31, just large enough to overflow. #
Yes, it would. Added.
Quoted 10 lines
commit-graph.c test_expect_success 'corrupt commit-graph write (missing tree)' ' · line 756
Quoted 5 lines
commit-graph.c +4
-0+test_expect_success 'overflow corrected commit date offset' ' + objdir=".git/objects" && +UNIX_EPOCH_ZERO ="1970-01-01 00:00 +0000" && + FUTURE_DATE="@2147483646 +0000" &&It is a bit funny to see
UNIX_EPOCH_ZEROspelled one way, andFUTURE_DATEother way.Wouldn't be more readable to use UNIX_EPOCH_ZERO="@0 +0000"?
It would, for some reason - I couldn't figure out the valid format for this. Changed.
Quoted 40 lines
Quoted 15 lines
commit-graph.c +14
-0+ test_oid_cache <<-EOF && + oid_version sha1:1 + oid_version sha256:2 + EOF + cd"$TRASH_DIRECTORY" && + mkdir repo && + cd repo && + git init && + test_commit_with_date 1 "$UNIX_EPOCH_ZERO" && + test_commit 2 && + test_commit_with_date 3 "$UNIX_EPOCH_ZERO" && + git commit-graph write --reachable && + graph_read_expect 3 generation_data && + test_commit_with_date 4 "$FUTURE_DATE" &&commit-graph.c test_expect_success 'corrupt commit-graph write (missing tree)' ' · line 780
Quoted 10 lines
commit-graph.c +10
-0+ test_commit 5 && + test_commit_with_date 6 "$UNIX_EPOCH_ZERO" && + git branch left && + git reset --hard 3 && + test_commit 7 && + test_commit_with_date 8 "$FUTURE_DATE" && + test_commit 9 && + git branch right && + git reset --hard 3 && + git merge left right &&We have
test_merge()function intest-lib-functions.sh, perhaps we should use it here.commit-graph.c test_expect_success 'corrupt commit-graph write (missing tree)' ' · line 782
Quoted 2 lines
commit-graph.c +2
-0+ git commit-graph write --reachable && + graph_read_expect 10 "generation_data generation_data_overflow" &&All right, we write the commit-graph and check that it has both GDAT and GDOV chunks present.
commit-graph.c test_expect_success 'corrupt commit-graph write (missing tree)' ' · line 783
Quoted 1 line
commit-graph.c +1
-0+ git commit-graph verifyAll right, we checks that created commit graph with GDAT and GDOV passes 'git commit-graph verify` checks.
Quoted 9 lines
commit-graph.c test_expect_success 'corrupt commit-graph write (missing tree)' ' · line 786
Quoted 3 lines
commit-graph.c +3
-0+' + +graph_git_behavior 'overflow corrected commit date offset' repo left rightAll right, here we compare the Git behavior with the commit-graph to the behavior without it... however I think that those two tests really should have distinct (different) test names. Currently they both use 'overflow corrected commit date offset'.
Following the earlier tests, the first test could be "set up and verify repo with generation data overflow chunk" and the git behavior test can be "generation data overflow chunk repo"
Quoted 86 lines
Quoted 24 lines
commit-graph.c +1
-0+ test_donet/t5324-split-commit-graph.sh +4
-4@@ -13,11 +13,11 @@ test_expect_success 'setup repo' ' infodir=".git/objects/info" && graphdir="$infodir/commit-graphs" && test_oid_cache <<-EOM - shallow sha1:1760 - shallow sha256:2064 + shallow sha1:2132 + shallow sha256:2436 - base sha1:1376 - base sha256:1496 + base sha1:1408 + base sha256:1528 oid_version sha1:1 oid_version sha256:2@@ -31,9 +31,9 @@ graph_read_expect() { NUM_BASE=$2t/t5324-split-commit-graph.sh graph_read_expect() { · line 39
Quoted 10 lines
t/t5324-split-commit-graph.sh +2
-2fi cat >expect <<- EOF - header: 43475048 1 $(test_oid oid_version) 3 $NUM_BASE + header: 43475048 1 $(test_oid oid_version) 4 $NUM_BASE num_commits: $1 - chunks: oid_fanout oid_lookup commit_metadata + chunks: oid_fanout oid_lookup commit_metadata generation_data EOF test-tool read-graph >output && test_cmp expect outputAll right, we now expect the commit graph to include the GDAT chunk... though shouldn't be there old expected value for no GDAT, for future tests? But perhaps this is not necessary.
Note that I have not checked the details, but it looks OK to me.
t/t6600-test-reach.sh test_expect_success 'setup' ' · line 60
Quoted 11 lines
t/t6600-test-reach.sh +3
-0@@ -55,10 +55,13 @@ test_expect_success 'setup' ' git show-ref -s commit-5-5 | git commit-graph write --stdin-commits && mv .git/objects/info/commit-graph commit-graph-half && chmod u+w commit-graph-half && + GIT_TEST_COMMIT_GRAPH_NO_GDAT=1 git commit-graph write --reachable && + mv .git/objects/info/commit-graph commit-graph-no-gdat && + chmod u+w commit-graph-no-gdat &&All right, this prepares for testing one more mode. The
run_all_modes()function would test the following cases:
- no commit-graph
- commit-graph for all commits, with GDAT
- commit-graph with half of commits, with GDAT
- commit-graph for all commits, without GDATQuoted 13 lines
t/t6600-test-reach.sh +2
-1git config core.commitGraph true ' -run_three_modes () { +run_all_modes () { test_when_finished rm -rf .git/objects/info/commit-graph && "$@" <input >actual && test_cmp expect actual &&@@ -67,11 +70,14 @@ run_three_modes () { test_cmp expect actual && cp commit-graph-half .git/objects/info/commit-graph && "$@" <input >actual && + test_cmp expect actual &&t/t6600-test-reach.sh run_three_modes () { · line 81
Quoted 10 lines
t/t6600-test-reach.sh +4
-2+ cp commit-graph-no-gdat .git/objects/info/commit-graph && + "$@" <input >actual && test_cmp expect actual } -test_three_modes () { - run_three_modes test-tool reach "$@" +test_all_modes () { + run_all_modes test-tool reach "$@" }All right.
Quoted 5 lines
Though to reduce "noise" in this patch, the rename of
run_three_modes()torun_all_modes()andtest_three_modes()totest_all_modes()could have been done in a separate preparatory patch. It would be pure refactoring patch, without introducing any new functionality.
Sure, that makes sense to me - this is patch is over 200 lines long already.
Quoted 1 line
...
Thanks
- Abhishek
Re: [PATCH v4 07/10] commit-graph: implement generation data chunk
In short: I think that because current implementation of writing GDOV chunk follows an example of writing EDGE chunk, it should be left as it is now (simple), and posible performance improvements be postponed to some future commit.
Quoted 1 line · Abhishek Kumar
On Fri, Oct 30, 2020 at 01:45:29PM +0100, Jakub Narębski wrote:
[...]
Quoted 8 lines
Quoted 8 lines
Quoted 7 lines
We test the overflow-related code with the following repo history:
F - N - U
/ \
U - N - U N
\ /
N - F - N
Quoted 7 lines
Quoted 3 lines
Do we need such complex history? I guess we need to test the handling of merge commits too.
I wanted to test three cases - a root epoch zero commit, a commit that's far enough in past to overflow the offset and a commit that's far enough in the future to overflow the offset.
All right, if I understand this correctly this would be U as root, U-F pair of commits and N-F pair of commits, respectively. Did I get it right?
Anyway, it might be a good idea to put this explanation in the commit message.
Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
Where the commits denoted by U have committer date of zero seconds since Unix epoch, the commits denoted by N have committer date of 1112354055 (default committer date for the test suite) seconds since Unix epoch and the commits denoted by F have committer date of (2 ^ 31 - 2) seconds since Unix epoch.
The largest offset observed is 2 ^ 31, just large enough to overflow.
[...]
Quoted 31 lines
Quoted 20 lines
Review static int write_graph_chunk_data(struct hashfile *f, · line 1155
Quoted 8 lines
(quoted diff) +4
-0@@ -1120,6 +1149,44 @@ static int write_graph_chunk_data(struct hashfile *f, return 0; } +static int write_graph_chunk_generation_data(struct hashfile *f, + struct write_commit_graph_context *ctx) +{ + int i, num_generation_data_overflows = 0;Minor nitpick: in my opinion there should be empty line here, between the variables declaration and the code... however not all write_graph_chunk_*() functions have it.
Review static int write_graph_chunk_data(struct hashfile *f, · line 1159
Quoted 4 lines
(quoted diff) +4
-0+ for (i = 0; i < ctx->commits.nr; i++) { + struct commit *c = ctx->commits.list[i]; + timestamp_t offset = commit_graph_data_at(c)->generation - c->date; + display_progress(ctx->progress, ++ctx->progress_cnt);All right.
Quoted 10 lines
Review static int write_graph_chunk_data(struct hashfile *f, · line 1164
Quoted 5 lines
(quoted diff) +5
-0+ + if (offset > GENERATION_NUMBER_V2_OFFSET_MAX) { + offset = CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW | num_generation_data_overflows; + num_generation_data_overflows++; + }Hmmm... shouldn't we store these commits that need overflow handling (with corrected commit date offset greater than
GENERATION_NUMBER_V2_OFFSET_MAX) in a list or a queue, to remember them for writing GDOV chunk?
Quoted 4 lines
We could, although
write_graph_chunk_extra_edges()(just like this function) prefers to iterate over all commits again. Both octopus merges and overflowing corrected commit dates are exceedingly rare, might be worthwhile to trade some memory to avoid looping again.
I'm sorry, I have not looked what write_graph_chunk_extra_edges() does, or rather how it does what it does -- it is a good idea to pattern your solution in similar existing code.
For me this is an even stronger hint that we should strive for simplicity first, and leave possible performance improvements for the future commit. Especially that you perform the most significant optimization for this overflow handling: ensuring that we do not perform any work if there are no commits with generation data overflow.
Maybe, maybe we should add that information about similarity between write_graph_chunk_generation_data_overflow() and write_graph_chunk_extra_edges() in the commit message. I am unsure...
Quoted 17 lines
Quoted 17 lines
We could store oids, or we could store commits themselves, for example:
if (offset > GENERATION_NUMBER_V2_OFFSET_MAX) { offset = CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW | num_generation_data_overflows; num_generation_data_overflows++; ALLOC_GROW(ctx->gdov_commits.list, ctx->gdov_commits.nr + 1, ctx->gdov_commits.alloc); ctx->commits.list[ctx->gdov_commits.nr] = c; ctx->gdov_commits.nr++; }Though in the above proposal we could get rid of
num_generation_data_overflows, as it should be the same asctx->gdov_commits.nr.I have called the extra commit list member of
write_commit_graph_contextgdov_commits, but perhaps a better name would becommits_gen_v2_overflow, or similar more descriptive name.
[...]
Quoted 34 lines
Quoted 14 lines
Review test_expect_success 'corrupt commit-graph write (missing tree)' ' · line 751
Quoted 11 lines
(quoted diff) +7
-0@@ -741,4 +742,47 @@ test_expect_success 'corrupt commit-graph write (missing tree)' ' ) ' +test_commit_with_date() { + file="$1.t" && + echo "$1" >"$file" && + git add "$file" && + GIT_COMMITTER_DATE="$2" GIT_AUTHOR_DATE="$2" git commit -m "$1" + git tag "$1" +}Here we add a helper function. All right.
Quoted 5 lines
I wonder though if it wouldn't be a better idea to add
--date <date>option to thetest_commit()function intest-lib-functions.sh(which option would setGIT_COMMITTER_DATEandGIT_AUTHOR_DATE, and also set notick=yes).Yes, that's a better idea - I didn't know how to change
test_commit()well enough to tinker with what's working.Quoted 11 lines
For example:
t/test-lib-functions.sh +1
-0@@ -202,6 +202,12 @@ test_commit () { --signoff) signoff="$1" ;; + --date)
Quoted 8 lines
t/test-lib-functions.sh test_commit () { · line 213
Quoted 8 lines
t/test-lib-functions.sh +5
-0+ notick=yes + GIT_COMMITTER_DATE="$2" + GIT_AUTHOR_DATE="$2" + shift + ;; -C) indir="$2" shift
Note however that I have while I have followed example of other options (namely '-C <directory>'), I have not actually tested this proposed implementation in tests; I have just tested that it looks like it works OK.
[...]
Quoted 8 lines
Quoted 8 lines
Review test_expect_success 'corrupt commit-graph write (missing tree)' ' · line 755
Quoted 4 lines
(quoted diff) +4
-0+test_expect_success 'overflow corrected commit date offset' ' + objdir=".git/objects" && + UNIX_EPOCH_ZERO="1970-01-01 00:00 +0000" && + FUTURE_DATE="@2147483646 +0000" &&It is a bit funny to see
UNIX_EPOCH_ZEROspelled one way, andFUTURE_DATEother way.
Quoted 4 lines
Quoted 1 line
Wouldn't be more readable to use UNIX_EPOCH_ZERO="@0 +0000"?
It would, for some reason - I couldn't figure out the valid format for this. Changed.
Well, if "@2147483646 +0000" works (i.e. "@<Unix epoch/timestamp> <offset>"), why the same for timestamp 0, i.e. "@0 +0000", wouldn't work?
[...]
Quoted 11 lines
Quoted 7 lines
Review test_expect_success 'corrupt commit-graph write (missing tree)' ' · line 756
Quoted 1 line
(quoted diff) +1
-0+graph_git_behavior 'overflow corrected commit date offset' repo left rightAll right, here we compare the Git behavior with the commit-graph to the behavior without it... however I think that those two tests really should have distinct (different) test names. Currently they both use 'overflow corrected commit date offset'.
Following the earlier tests, the first test could be "set up and verify repo with generation data overflow chunk" and the git behavior test can be "generation data overflow chunk repo"
First is OK, the second could possibly be improved but is all right.
[...]
Quoted 8 lines
Quoted 5 lines
Though to reduce "noise" in this patch, the rename of
run_three_modes()torun_all_modes()andtest_three_modes()totest_all_modes()could have been done in a separate preparatory patch. It would be pure refactoring patch, without introducing any new functionality.Sure, that makes sense to me - this is patch is over 200 lines long already.
Thanks in advance.
Best,
Signature · 1 line
Jakub Narębski
PATCH v4 8/10 commit-graph: use generation v2 only if entire chain does
In reply to Abhishek Kumar via GitGitGadgetFrom: Abhishek Kumar <[email protected]>
Since there are released versions of Git that understand generation numbers in the commit-graph's CDAT chunk but do not understand the GDAT chunk, the following scenario is possible:
1. "New" Git writes a commit-graph with the GDAT chunk.
2. "Old" Git writes a split commit-graph on top without a GDAT chunk.
Because of the current use of inspecting the current layer for a chunk_generation_data pointer, the commits in the lower layer will be interpreted as having very large generation values (commit date plus offset) compared to the generation numbers in the top layer (topological level). This violates the expectation that the generation of a parent is strictly smaller than the generation of a child.
It is difficult to expose this issue in a test. Since we _start_ with artificially low generation numbers, any commit walk that prioritizes generation numbers will walk all of the commits with high generation number before walking the commits with low generation number. In all the cases I tried, the commit-graph layers themselves "protect" any incorrect behavior since none of the commits in the lower layer can reach the commits in the upper layer.
This issue would manifest itself as a performance problem in this case, especially with something like "git log --graph" since the low generation numbers would cause the in-degree queue to walk all of the commits in the lower layer before allowing the topo-order queue to write anything to output (depending on the size of the upper layer).
When writing the new layer in split commit-graph, we write a GDAT chunk only if the topmost layer has a GDAT chunk. This guarantees that if a layer has GDAT chunk, all lower layers must have a GDAT chunk as well.
Rewriting layers follows similar approach: if the topmost layer below the set of layers being rewritten (in the split commit-graph chain) exists, and it does not contain GDAT chunk, then the result of rewrite does not have GDAT chunks either.
--- commit-graph.c | 29 +++++++++++- commit-graph.h | 1 + t/t5324-split-commit-graph.sh | 86 +++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 1 deletion(-)
commit-graph.c +28 -1
@@ -605,6 +605,21 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r,return graph_chain; } +static void validate_mixed_generation_chain (struct commit_graph *g) +{ +int read_generation_data; + +if (!g) +return ; + + read_generation_data = !!g->chunk_generation_data; + +while (g) { + g->read_generation_data = read_generation_data; + g = g->base_graph; + } +} +struct commit_graph *read_commit_graph_one (struct repository *r,struct object_directory *odb) {
@@ -613,6 +628,8 @@ struct commit_graph *read_commit_graph_one(struct repository *r,if (!g) g =load_commit_graph_chain (r, odb); +validate_mixed_generation_chain (g); +return g; }
@@ -782,7 +799,7 @@ static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, date_low =get_be32 (commit_data + g->hash_len +12 ); item->date = (timestamp_t)((date_high <<32 ) | date_low); -if (g->chunk_generation_data) { +if (g->chunk_generation_data && g->read_generation_data) { offset = (timestamp_t )get_be32 (g->chunk_generation_data +sizeof (uint32_t ) * lex_index);if (offset &CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW ) {
@@ -2030,6 +2047,9 @@ static void split_graph_merge_strategy(struct write_commit_graph_context *ctx) } } +if (!ctx->write_generation_data && g->chunk_generation_data) + ctx->write_generation_data =1 ; +if (flags !=COMMIT_GRAPH_SPLIT_REPLACE ) ctx->new_base_graph = g;else if (ctx->num_commit_graphs_after !=1 )
@@ -2274,6 +2294,7 @@ int write_commit_graph(struct object_directory *odb,struct commit_graph *g = ctx->r->objects->commit_graph;while (g) { + g->read_generation_data =1 ; g->topo_levels = &topo_levels; g = g->base_graph; }
@@ -2300,6 +2321,9 @@ int write_commit_graph(struct object_directory *odb, g = ctx->r->objects->commit_graph; +if (g && !g->chunk_generation_data) + ctx->write_generation_data =0 ; +while (g) { ctx->num_commit_graphs_before++; g = g->base_graph;
@@ -2318,6 +2342,9 @@ int write_commit_graph(struct object_directory *odb,if (ctx->opts) replace = ctx->opts->split_flags &COMMIT_GRAPH_SPLIT_REPLACE ; + +if (replace) + ctx->write_generation_data =1 ; } ctx->approx_nr_objects =approximate_object_count ();
commit-graph.h +1 -0
@@ -64,6 +64,7 @@ struct commit_graph {struct object_directory *odb;uint32_t num_commits_in_base; +unsigned int read_generation_data;struct commit_graph *base_graph;const uint32_t *chunk_oid_fanout;
t/t5324-split-commit-graph.sh +86 -0
90 lines not on this page · raw message
Signature · 1 line
gitgitgadget
Re: [PATCH v4 08/10] commit-graph: use generation v2 only if entire chain does
Hi Abhishek,
Quoted 8 lines · Abhishek Kumar via GitGitGadget
From: Abhishek Kumar <[email protected]>
Since there are released versions of Git that understand generation numbers in the commit-graph's CDAT chunk but do not understand the GDAT chunk, the following scenario is possible:
1. "New" Git writes a commit-graph with the GDAT chunk.
2. "Old" Git writes a split commit-graph on top without a GDAT chunk.
All right.
Quoted 7 lines
Because of the current use of inspecting the current layer for a
chunk_generation_datapointer, the commits in the lower layer will be interpreted as having very large generation values (commit date plus offset) compared to the generation numbers in the top layer (topological level). This violates the expectation that the generation of a parent is strictly smaller than the generation of a child.
I think this paragraphs tries too much to be concise, with the result it is less clear than it could be. Perhaps it would be better to separate "what-if" from the current behavior.
If each layer of split commit-graph is treated independently, as it were the case before this commit, with Git inspecting only the current layer for chunk_generation_data pointer, commits in the lower layer (one with GDAT) would have corrected commit date as their generation number, while commits in the upper layer would have topological levels as their generation. Corrected commit dates have usually much larger values than topological levels. This means that if we take two commits, one from the upper layer, and one reachable from it in the lower layer, then the expectation that the generation of a parent is smaller than the generation of a child would be violated.
Quoted 8 lines
It is difficult to expose this issue in a test. Since we _start_ with artificially low generation numbers, any commit walk that prioritizes generation numbers will walk all of the commits with high generation number before walking the commits with low generation number. In all the cases I tried, the commit-graph layers themselves "protect" any incorrect behavior since none of the commits in the lower layer can reach the commits in the upper layer.
I don't quite understand the issue here. Unless none of the following query commands short-circuit and all walk the commit graph regardless of what generation numbers tell them, they should give different results with and without the commit graph, if we take two commits one from lower layer of split commit graph with GDAT, and one commit from the higher layer without GDAT, one lower reachable from the other higher.
We have the following query commands that we can check:
$ git merge-base --is-ancestor <lower> <higher> $ git merge-base --independent <lower> <higher>
$ git tag --contains <tag-to-lower> $ git tag --merged <tag-to-higher> $ git branch --contains <branch-to-lower> $ git branch --merged <branch-to-higher>
The second set of queries require for those commits to be tagged, or have branch pointing at them, respectively.
Also, shouldn't git commit-graph verify fail with split commit graph where the top layer is created with GIT_TEST_COMMIT_GRAPH_NO_GDAT=1?
Let's assume that we have the following history, with newer commits shown on top like in git log --graph --oneline --all:
topological corrected generation
level commit date number^* d 3 3
|
c | 3 3 | | without GDAT ..|..|.....[layer.boundary]........................................ | | with GDAT | b 2 1112912113 1112912113 | | a | 2 1112912053 1112912053
| /
|/
r 1 1112911993 1112911993
*) each layer inspected individually.
With such history, we can for example reach 'a' from 'c', thus git merge-base --is-ancestor a b should return true value, but without this commit gen(a) > gen(c), instead of gen(a) <= gen(c); I use here weaker reachability condition, but the one that works also for commits outside the commit-graph (and those for which generation numbers overflows).
Quoted 6 lines
This issue would manifest itself as a performance problem in this case, especially with something like "git log --graph" since the low generation numbers would cause the in-degree queue to walk all of the commits in the lower layer before allowing the topo-order queue to write anything to output (depending on the size of the upper layer).
All right, that's good explanation.
Quoted 9 lines
When writing the new layer in split commit-graph, we write a GDAT chunk only if the topmost layer has a GDAT chunk. This guarantees that if a layer has GDAT chunk, all lower layers must have a GDAT chunk as well.
Rewriting layers follows similar approach: if the topmost layer below the set of layers being rewritten (in the split commit-graph chain) exists, and it does not contain GDAT chunk, then the result of rewrite does not have GDAT chunks either.
All right, very good explanation; the only minor suggestion would be to add some 'intro' to the first of those two paragraphs, for example:
Therefore, when writing the new layer in split commit-graph...
Though I am not sure if it is necessary.
Quoted 21 lines
Signed-off-by Derrick Stolee <[email protected]>Signed-off-by Abhishek Kumar <[email protected]>--- commit-graph.c | 29 +++++++++++- commit-graph.h | 1 + t/t5324-split-commit-graph.sh | 86 +++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 1 deletion(-)commit-graph.c +4
-0@@ -605,6 +605,21 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r,return graph_chain; } +static void validate_mixed_generation_chain (struct commit_graph *g) +{ +int read_generation_data; +
commit-graph.c static struct commit_graph *load_commit_graph_chain(struct repository *r, · line 621
Quoted 10 lines
commit-graph.c +10
-0+if (!g) +return ; + + read_generation_data = !!g->chunk_generation_data; + +while (g) { + g->read_generation_data = read_generation_data; + g = g->base_graph; + } +}
All right, this function checks assumedly topmost layer if it is GDAT-less, and if it is propagates this status down the layers of split commit graph. This is needed because if we have mixed-generation commit graph, then for each and every layer we need to use topological levels as generation number.
The only minor issue is the name of this function (it does not hint that it propagates the GDAT status downwards), but I don't have a better idea, unfortunately. And it does reflect what this function is used for.
commit-graph.c struct commit_graph *read_commit_graph_one(struct repository *r, · line 632
Quoted 10 lines
commit-graph.c +3
-0+struct commit_graph *read_commit_graph_one (struct repository *r,struct object_directory *odb) {@@ -613,6 +628,8 @@ struct commit_graph *read_commit_graph_one(struct repository *r,if (!g) g =load_commit_graph_chain (r, odb); +validate_mixed_generation_chain (g); +
All right, this looks like a good place to put this new check: just after reading commit-graph chain.
commit-graph.c static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, · line 803
Quoted 10 lines
commit-graph.c +1
-1return g; }@@ -782,7 +799,7 @@ static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, date_low =get_be32 (commit_data + g->hash_len +12 ); item->date = (timestamp_t)((date_high <<32 ) | date_low); -if (g->chunk_generation_data) { +if (g->chunk_generation_data && g->read_generation_data) { offset = (timestamp_t )get_be32 (g->chunk_generation_data +sizeof (uint32_t ) * lex_index);
All right, instead of simply checking if the current layer has generation data chunk, we need to also check if the whole graph allows for it (if there are no mixed-generation layers).
The g->read_generation_data should be filled correctly, because fill_commit_graph_info() is always preceded by read_commit_graph(), if I understand it correctly.
commit-graph.c static void split_graph_merge_strategy(struct write_commit_graph_context *ctx) · line 2052
Quoted 9 lines
commit-graph.c +3
-0if (offset &CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW ) {@@ -2030,6 +2047,9 @@ static void split_graph_merge_strategy(struct write_commit_graph_context *ctx) } } +if (!ctx->write_generation_data && g->chunk_generation_data) + ctx->write_generation_data =1 ; +
This needs more careful examination, and looking at larger context of those lines.
At this point, unless --split=replace option is used, 'g' points to the bottom layer out of all topmost layers being merged. We know that if there are GDAT-less layers then these must be top layers, so this means that we can write GDAT chunk in the result of the merge -- because we would be replacing all possible GDAT-less layers (and maybe some with GDAT) with a single layer with the GDAT chunk.
The ctx->write_generation_data is set to true unless environment variable GIT_TEST_COMMIT_GRAPH_NO_GDAT is true, and that in write_commit_graph() it would be set to false if topmost layer doesn't have GDAT chunk, and to true if --split=replace option is used; see below.
Looks good to me.
NOTE that this means that GIT_TEST_COMMIT_GRAPH_NO_GDAT prevents from writing GDAT chunk with generation data v2 unless we are merging layers, or replacing all of them with a single layer: then it is _ignored_.
Should we clarify this fact in the description of GIT_TEST_COMMIT_GRAPH_NO_GDAT in t/README? Currently it reads:
GIT_TEST_COMMIT_GRAPH_NO_GDAT=<boolean>, when true, forces the commit-graph to be written without generation data chunk.
commit-graph.c int write_commit_graph(struct object_directory *odb, · line 2300
Quoted 11 lines
commit-graph.c +1
-0if (flags !=COMMIT_GRAPH_SPLIT_REPLACE ) ctx->new_base_graph = g;else if (ctx->num_commit_graphs_after !=1 )@@ -2274,6 +2294,7 @@ int write_commit_graph(struct object_directory *odb,struct commit_graph *g = ctx->r->objects->commit_graph;while (g) { + g->read_generation_data =1 ; g->topo_levels = &topo_levels; g = g->base_graph; }
All right, when writing the commit graph we want to make use of existing generation data chunks. This is safe, because when computing generation numbers for writing we have separate place to store topoogical levels (topo_levels) so they would not be mixed with corrected commit dates:
generation number v1 and v2 are kept separate.
commit-graph.c int write_commit_graph(struct object_directory *odb, · line 2326
Quoted 7 lines
commit-graph.c +3
-0@@ -2300,6 +2321,9 @@ int write_commit_graph(struct object_directory *odb, g = ctx->r->objects->commit_graph; +if (g && !g->chunk_generation_data) + ctx->write_generation_data =0 ; +
All right, if current (topmost) layed does not have GDAT, then when creating a new layer do not create GDAT layer either (merging layers and rewriting the commit-graph is handled separately).
commit-graph.c int write_commit_graph(struct object_directory *odb, · line 2347
Quoted 10 lines
commit-graph.c +3
-0while (g) { ctx->num_commit_graphs_before++; g = g->base_graph;@@ -2318,6 +2342,9 @@ int write_commit_graph(struct object_directory *odb,if (ctx->opts) replace = ctx->opts->split_flags &COMMIT_GRAPH_SPLIT_REPLACE ; + +if (replace) + ctx->write_generation_data =1 ;
All right, when replacing all layers (git commit-graph write --split=replace), then we can safely write the GDAT chunk.
Note however that here we don't take into account the value of the environment variable GIT_TEST_COMMIT_GRAPH_NO_GDAT. Which maybe is what we want...
commit-graph.h struct commit_graph { · line 68
Quoted 13 lines
commit-graph.c +0
-0} ctx->approx_nr_objects =approximate_object_count ();commit-graph.h +1
-0@@ -64,6 +64,7 @@ struct commit_graph {struct object_directory *odb;uint32_t num_commits_in_base; +unsigned int read_generation_data;struct commit_graph *base_graph;
All right, this new field is here to propagate to each layer the information whether we can read from the generation number v2 data chunk.
Though I am not sure whether this field should be added here, and whether it should be unsigned int (we don't have to be that careful about saving space for this type).
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 444
Quoted 12 lines
commit-graph.h +0
-0const uint32_t *chunk_oid_fanout;t/t5324-split-commit-graph.sh +2
-0@@ -440,4 +440,90 @@ test_expect_success '--split=replace with partial Bloom data' ' verify_chain_files_exist $graphdir ' +test_expect_success 'setup repo for mixed generation commit-graph-chain' ' + mkdir mixed &&
This should probably go just before cd-ing into just created subdirectory.
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 449
Quoted 5 lines
t/t5324-split-commit-graph.sh +5
-0+ graphdir=".git/objects/info/commit-graphs" && + test_oid_cache <<-EOM && + oid_version sha1:1 + oid_version sha256:2 + EOM
Minor nitpick: Why use "EOM", which is used only twice in Git the test suite, and not the conventional "EOF" (used at least 4000 times)?
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 450
Quoted 1 line
t/t5324-split-commit-graph.sh +1
-0+ cd "$TRASH_DIRECTORY/mixed" &&
The t/README says:
- Don't chdir around in tests. It is not sufficient to chdir to somewhere and then chdir back to the original location later in the test, as any intermediate step can fail and abort the test, causing the next test to start in an unexpected directory. Do so inside a subshell if necessary.
Though I am not sure if it should apply also to this situation.
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 453
Quoted 3 lines
t/t5324-split-commit-graph.sh +3
-0+ git init && + git config core.commitGraph true && + git config gc.writeCommitGraph false &&
All right.
Quoted 8 lines
t/t5324-split-commit-graph.sh +8
-0+ for i in $(test_seq 3) + do + test_commit $i && + git branch commits/$i || return 1 + done && + git reset --hard commits/1 && + for i in $(test_seq 4 5) + do
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 471
Quoted 10 lines
t/t5324-split-commit-graph.sh +10
-0+ test_commit $i && + git branch commits/$i || return 1 + done && + git reset --hard commits/2 && + for i in $(test_seq 6 10) + do + test_commit $i && + git branch commits/$i || return 1 + done && + git commit-graph write --reachable --split &&
Is there a reason why we do not check just written commit-graph file with test-tool read-graph >output-layer-1?
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 473
Quoted 2 lines
t/t5324-split-commit-graph.sh +2
-0+ git reset --hard commits/2 && + git merge commits/4 &&
Shouldn't we use test_merge instead of git merge; I am not sure when to use one or the other?
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 477
Quoted 4 lines
t/t5324-split-commit-graph.sh +4
-0+ git branch merge/1 && + git reset --hard commits/4 && + git merge commits/6 && + git branch merge/2 &&
It would be nice to have ASCII-art of the history (of the graph of revisions) created here for subsequent tests:
/- 6 <-- 7 <-- 8 <-- 9 <-- 10*
/ \-\
/ \
1 <-- 2 <-- 3* \--\
| \ \
| \-----\ \
\ \ \
\-- 4*<------ M/1 M/2
|\ /
| \-- 5* /
\ /
\------------/* - 1st layer
Though I am not sure if what I have created is readable; I think a better way to draw this graph is possible, for example:
/- 3*
/
/
1 <------ 2 <---- 6 <-- 7 <-- 8 <-- 9 <-- 10*
\ \ \
\ \ \
\ \ \
\- 4* <-- M/1 \
|\ \
| \------------- M/2
\
\---- 5*Edit: as I see the history gets even more complicated, so perhaps ASCII-art diagram of the history with layers marked would be too complicated, and wouldn't bring much.
Why do we need such shape of the history in the repository?
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 485
Quoted 8 lines
t/t5324-split-commit-graph.sh +8
-0+ GIT_TEST_COMMIT_GRAPH_NO_GDAT=1 git commit-graph write --reachable --split=no-merge && + test-tool read-graph >output && + cat >expect <<-EOF && + header: 43475048 1 $(test_oid oid_version) 4 1 + num_commits: 2 + chunks: oid_fanout oid_lookup commit_metadata + EOF + test_cmp expect output &&
All right, we check that we have 2 commits, and that there is no GDAT chunk.
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 486
Quoted 1 line
t/t5324-split-commit-graph.sh +1
-0+ git commit-graph verify
All right, we verify commit-graph as a whole (both layers).
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 489
Quoted 3 lines
t/t5324-split-commit-graph.sh +3
-0+' + +test_expect_success 'does not write generation data chunk if not present on existing tip' '
Hmmm... I wonder if we can come up with a better name for this test; for example should it be "does not write" or "do not write"?
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 495
Quoted 6 lines
t/t5324-split-commit-graph.sh +6
-0+ cd "$TRASH_DIRECTORY/mixed" && + git reset --hard commits/3 && + git merge merge/1 && + git merge commits/5 && + git merge merge/2 && + git branch merge/3 &&
The commit graph gets complicated, so it would not be easy to visualize it with ASCII-art diagram without any crossed lines. Maybe git log --graph --oneline --all would help:
* (merge/3) Merge branch 'merge/2'
|\ | * (merge/2) Merge branch 'commits/6' | |\ * | \ Merge branch 'commits/5' |\ \ \ | * | | (commits/5) 5 | |/ / * | | Merge branch 'merge/1' |\ \ \ | * | | (merge/1) Merge branch 'commits/4' | |\| | | | * | (commits/4) 4 * | | | (commits/3) 3 |/ / / | | | * (commits/10) 10 | | | * (commits/9) 9 | | | * (commits/8) 8 | | | * (commits/7) 7 | | |/ | | * (commits/6) 6 | |/ |/|
* | (commits/2) 2
|/
* (commits/1) 1
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 504
Quoted 9 lines
t/t5324-split-commit-graph.sh +9
-0+ git commit-graph write --reachable --split=no-merge && + test-tool read-graph >output && + cat >expect <<-EOF && + header: 43475048 1 $(test_oid oid_version) 4 2 + num_commits: 3 + chunks: oid_fanout oid_lookup commit_metadata + EOF + test_cmp expect output && + git commit-graph verify
All right, so here we check that we have layer without GDAT at the top, and we request not to merge layers thus new layer will be created, then the new layer also does not have GDAT chunk (and has 3 commits).
Minor nitpick: shouldn't those test be indented?
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 512
Quoted 8 lines
t/t5324-split-commit-graph.sh +8
-0+' + +test_expect_success 'writes generation data chunk when commit-graph chain is replaced' ' + cd "$TRASH_DIRECTORY/mixed" && + git commit-graph write --reachable --split=replace && + test_path_is_file $graphdir/commit-graph-chain && + test_line_count = 1 $graphdir/commit-graph-chain && + verify_chain_files_exist $graphdir &&
All right, this checks that we have split commit-graph chain that consist of a single layer, and that the commit-graph file for this single layer exists.
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 513
Quoted 1 line
t/t5324-split-commit-graph.sh +1
-0+ graph_read_expect 15 &&
Shouldn't we use test-tool read-graph to check whether generation_data chunk is present... ah, sorry, I have realized that after previous patches graph_read_expect 15 implicitly checks the latter, because in its' use of test-tool read-graph it does expect generation_data chunk.
So we use test-tool read-graph manually to check that generation_data chunk is absent, and we use graph_read_expect to check that it is present (and in both cases that the number of commits matches). I wonder if it would be possible to simplify that...
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 514
Quoted 1 line
t/t5324-split-commit-graph.sh +1
-0+ git commit-graph verify
All right.
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 527
Quoted 13 lines
t/t5324-split-commit-graph.sh +13
-0+' + +test_expect_success 'add one commit, write a tip graph' ' + cd "$TRASH_DIRECTORY/mixed" && + test_commit 11 && + git branch commits/11 && + git commit-graph write --reachable --split && + test_path_is_missing $infodir/commit-graph && + test_path_is_file $graphdir/commit-graph-chain && + ls $graphdir/graph-*.graph >graph-files && + test_line_count = 2 graph-files && + verify_chain_files_exist $graphdir +'
What it is meant to test? That adding single-commit to a 15 commit commit-graph file in split mode does not result in layers merging, and actually adds a new layer: we check that we have exactly two layers and that they are all OK.
We don't check here that the newly created top layer commit-graph does have GDAT chunk, as it should be if the top layer (in this case the only layer) has GDAT chunk.
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 529
Quoted 2 lines
t/t5324-split-commit-graph.sh +1
-0+ test_done
One test we are missing is testing that merging layers is done correctly, namely that if we are merging layers in split commit-graph file, and the layer below the ones we are merging lacks GDAT chunk, then the result of the merge should also be without GDAT chunk. This would require at least two GDAT-less layers in a setup.
I'm not sure how difficult writing such test should be.
Best,
Signature · 1 line
Jakub Narębski
Re: [PATCH v4 08/10] commit-graph: use generation v2 only if entire chain does
Quoted 26 lines · Jakub Narębski
Hi Abhishek,
Quoted 8 lines · Abhishek Kumar via GitGitGadget
From: Abhishek Kumar <[email protected]>
Since there are released versions of Git that understand generation numbers in the commit-graph's CDAT chunk but do not understand the GDAT chunk, the following scenario is possible:
1. "New" Git writes a commit-graph with the GDAT chunk.
2. "Old" Git writes a split commit-graph on top without a GDAT chunk.All right.
Quoted 7 lines
Because of the current use of inspecting the current layer for a
chunk_generation_datapointer, the commits in the lower layer will be interpreted as having very large generation values (commit date plus offset) compared to the generation numbers in the top layer (topological level). This violates the expectation that the generation of a parent is strictly smaller than the generation of a child.I think this paragraphs tries too much to be concise, with the result it is less clear than it could be. Perhaps it would be better to separate "what-if" from the current behavior.
Quoted 11 lines
If each layer of split commit-graph is treated independently, as it were the case before this commit, with Git inspecting only the current layer for
chunk_generation_datapointer, commits in the lower layer (one with GDAT) would have corrected commit date as their generation number, while commits in the upper layer would have topological levels as their generation. Corrected commit dates have usually much larger values than topological levels. This means that if we take two commits, one from the upper layer, and one reachable from it in the lower layer, then the expectation that the generation of a parent is smaller than the generation of a child would be violated.
Thanks, that's better.
Quoted 50 lines
Quoted 8 lines
It is difficult to expose this issue in a test. Since we _start_ with artificially low generation numbers, any commit walk that prioritizes generation numbers will walk all of the commits with high generation number before walking the commits with low generation number. In all the cases I tried, the commit-graph layers themselves "protect" any incorrect behavior since none of the commits in the lower layer can reach the commits in the upper layer.
I don't quite understand the issue here. Unless none of the following query commands short-circuit and all walk the commit graph regardless of what generation numbers tell them, they should give different results with and without the commit graph, if we take two commits one from lower layer of split commit graph with GDAT, and one commit from the higher layer without GDAT, one lower reachable from the other higher.
We have the following query commands that we can check:
$ git merge-base --is-ancestor <lower> <higher> $ git merge-base --independent <lower> <higher>$ git tag --contains <tag-to-lower> $ git tag --merged <tag-to-higher> $ git branch --contains <branch-to-lower> $ git branch --merged <branch-to-higher>The second set of queries require for those commits to be tagged, or have branch pointing at them, respectively.
Also, shouldn't
git commit-graph verifyfail with split commit graph where the top layer is created with GIT_TEST_COMMIT_GRAPH_NO_GDAT=1?Let's assume that we have the following history, with newer commits shown on top like in
git log --graph --oneline --all:topological corrected generation level commit date number^*d 3 3
|c | 3 3 | | without GDAT ..|..|.....[layer.boundary]........................................ | | with GDAT | b 2 1112912113 1112912113 | | a | 2 1112912053 1112912053| /
|/
r 1 1112911993 1112911993
Quoted 9 lines
*) each layer inspected individually.
With such history, we can for example reach 'a' from 'c', thus
git merge-base --is-ancestor a bshould return true value, but without this commit gen(a) > gen(c), instead of gen(a) <= gen(c); I use here weaker reachability condition, but the one that works also for commits outside the commit-graph (and those for which generation numbers overflows).
The original explanation was given by Dr. Stolee and he might not have thought exhaustively about the issue.
In any case, your explanation and the history make sense to me. I will try to add test and report back to the mailing list if something goes wrong.
Thank you for clarifying in such detail.
Quoted 42 lines
Quoted 6 lines
This issue would manifest itself as a performance problem in this case, especially with something like "git log --graph" since the low generation numbers would cause the in-degree queue to walk all of the commits in the lower layer before allowing the topo-order queue to write anything to output (depending on the size of the upper layer).
All right, that's good explanation.
...
commit-graph.c static void split_graph_merge_strategy(struct write_commit_graph_context *ctx) · line 2052
Quoted 7 lines
commit-graph.c +3
-0@@ -2030,6 +2047,9 @@ static void split_graph_merge_strategy(struct write_commit_graph_context *ctx) } } +if (!ctx->write_generation_data && g->chunk_generation_data) + ctx->write_generation_data =1 ; +This needs more careful examination, and looking at larger context of those lines.
At this point, unless
--split=replaceoption is used, 'g' points to the bottom layer out of all topmost layers being merged. We know that if there are GDAT-less layers then these must be top layers, so this means that we can write GDAT chunk in the result of the merge -- because we would be replacing all possible GDAT-less layers (and maybe some with GDAT) with a single layer with the GDAT chunk.The
ctx->write_generation_datais set to true unless environment variableGIT_TEST_COMMIT_GRAPH_NO_GDATis true, and that inwrite_commit_graph()it would be set to false if topmost layer doesn't have GDAT chunk, and to true if--split=replaceoption is used; see below.Looks good to me.
NOTE that this means that
GIT_TEST_COMMIT_GRAPH_NO_GDATprevents from writing GDAT chunk with generation data v2 unless we are merging layers, or replacing all of them with a single layer: then it is _ignored_.
Quoted 5 lines
Should we clarify this fact in the description of
GIT_TEST_COMMIT_GRAPH_NO_GDATin t/README? Currently it reads:GIT_TEST_COMMIT_GRAPH_NO_GDAT=<boolean>, when true, forces the commit-graph to be written without generation data chunk.
I think it's better to *not* write generation data chunk if GIT_TEST_COMMIT_GRAPH_NO_GDAT is set even though all GDAT-less layers are merged, that is:
if (!ctx->write_generation_data &&
g->chunk_generation_data &&
!git_env_bool(GIT_TEST_COMMIT_GRAPH_NO_GDAT, 0))
ctx->write_generation_data = 1;With this change, we would have a method to force-write commit-graph without generation data chunk regardless of the shape of split commit-graph files.
Quoted 18 lines
...
commit-graph.h struct commit_graph { · line 68
Quoted 10 lines
commit-graph.h +1
-0@@ -64,6 +64,7 @@ struct commit_graph {struct object_directory *odb;uint32_t num_commits_in_base; +unsigned int read_generation_data;struct commit_graph *base_graph;All right, this new field is here to propagate to each layer the information whether we can read from the generation number v2 data chunk.
Quoted 4 lines
Though I am not sure whether this field should be added here, and whether it should be
unsigned int(we don't have to be that careful about saving space for this type).
I cannot think of a more appropriate struct than struct commit_graph. Any particular suggestions?
Quoted 16 lines
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 444
Quoted 12 lines
commit-graph.h +0
-0const uint32_t *chunk_oid_fanout;t/t5324-split-commit-graph.sh +2
-0@@ -440,4 +440,90 @@ test_expect_success '--split=replace with partial Bloom data' ' verify_chain_files_exist $graphdir ' +test_expect_success 'setup repo for mixed generation commit-graph-chain' ' + mkdir mixed &&This should probably go just before cd-ing into just created subdirectory.
Quoted 8 lines
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 449
Quoted 5 lines
t/t5324-split-commit-graph.sh +5
-0+ graphdir=".git/objects/info/commit-graphs" && + test_oid_cache <<-EOM && + oid_version sha1:1 + oid_version sha256:2 + EOMMinor nitpick: Why use "EOM", which is used only twice in Git the test suite, and not the conventional "EOF" (used at least 4000 times)?
Right, both instances of "EOM" are actually my own. I looked up some test script for oid cache that did use EOM when I first wrote the tests but it's changed now. Will replace.
Quoted 5 lines
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 450
Quoted 2 lines
t/t5324-split-commit-graph.sh +1
-0+ cd "$TRASH_DIRECTORY/mixed" &&The t/README says:
Quoted 7 lines
- Don't chdir around in tests. It is not sufficient to chdir to somewhere and then chdir back to the original location later in the test, as any intermediate step can fail and abort the test, causing the next test to start in an unexpected directory. Do so inside a subshell if necessary.
Though I am not sure if it should apply also to this situation.
While I cannot avoid changing directory, using a subshell would be best to avoid causing the later tests to start in unexpected directories.
Quoted 20 lines
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 453
Quoted 4 lines
t/t5324-split-commit-graph.sh +3
-0+ git init && + git config core.commitGraph true && + git config gc.writeCommitGraph false &&All right.
Quoted 13 lines
t/t5324-split-commit-graph.sh +13
-0+ for i in $(test_seq 3) + do + test_commit $i && + git branch commits/$i || return 1 + done && + git reset --hard commits/1 && + for i in $(test_seq 4 5) + do + test_commit $i && + git branch commits/$i || return 1 + done && + git reset --hard commits/2 && + for i in $(test_seq 6 10)
Quoted 8 lines
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 471
Quoted 5 lines
t/t5324-split-commit-graph.sh +5
-0+ do + test_commit $i && + git branch commits/$i || return 1 + done && + git commit-graph write --reachable --split &&Is there a reason why we do not check just written commit-graph file with
test-tool read-graph >output-layer-1?
We could check the written commit-graph file at this point but it's same as existing tests as above.
Quoted 6 lines
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 473
Quoted 3 lines
t/t5324-split-commit-graph.sh +2
-0+ git reset --hard commits/2 && + git merge commits/4 &&Shouldn't we use
test_mergeinstead ofgit merge; I am not sure when to use one or the other?
test_merge is used in 26 places whereas git merge is used in over a thousand places. test_merge is just not widely adopted and this lack of adoption prevents further use.
Quoted 41 lines
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 477
Quoted 5 lines
t/t5324-split-commit-graph.sh +4
-0+ git branch merge/1 && + git reset --hard commits/4 && + git merge commits/6 && + git branch merge/2 &&It would be nice to have ASCII-art of the history (of the graph of revisions) created here for subsequent tests:
/- 6 <-- 7 <-- 8 <-- 9 <-- 10* / \-\ / \ 1 <-- 2 <-- 3* \--\ | \ \ | \-----\ \ \ \ \ \-- 4*<------ M/1 M/2 |\ / | \-- 5* / \ / \------------/* - 1st layer
Though I am not sure if what I have created is readable; I think a better way to draw this graph is possible, for example:
/- 3* / / 1 <------ 2 <---- 6 <-- 7 <-- 8 <-- 9 <-- 10* \ \ \ \ \ \ \ \ \ \- 4* <-- M/1 \ |\ \ | \------------- M/2 \ \---- 5*
Quoted 5 lines
Edit: as I see the history gets even more complicated, so perhaps ASCII-art diagram of the history with layers marked would be too complicated, and wouldn't bring much.
Why do we need such shape of the history in the repository?
We don't need such a complicated shape. Any commit-graph file with 2-3 layers regardless of how commits are related should suffice. Will simplify.
Quoted 17 lines
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 485
Quoted 9 lines
t/t5324-split-commit-graph.sh +8
-0+ GIT_TEST_COMMIT_GRAPH_NO_GDAT=1 git commit-graph write --reachable --split=no-merge && + test-tool read-graph >output && + cat >expect <<-EOF && + header: 43475048 1 $(test_oid oid_version) 4 1 + num_commits: 2 + chunks: oid_fanout oid_lookup commit_metadata + EOF + test_cmp expect output &&All right, we check that we have 2 commits, and that there is no GDAT chunk.
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 486
Quoted 1 line
t/t5324-split-commit-graph.sh +1
-0+ git commit-graph verifyAll right, we verify commit-graph as a whole (both layers).
Quoted 6 lines
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 489
Quoted 3 lines
t/t5324-split-commit-graph.sh +3
-0+' + +test_expect_success 'does not write generation data chunk if not present on existing tip' 'Hmmm... I wonder if we can come up with a better name for this test; for example should it be "does not write" or "do not write"?
That's better.
Quoted 54 lines
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 495
Quoted 7 lines
t/t5324-split-commit-graph.sh +6
-0+ cd "$TRASH_DIRECTORY/mixed" && + git reset --hard commits/3 && + git merge merge/1 && + git merge commits/5 && + git merge merge/2 && + git branch merge/3 &&The commit graph gets complicated, so it would not be easy to visualize it with ASCII-art diagram without any crossed lines. Maybe
git log --graph --oneline --allwould help:* (merge/3) Merge branch 'merge/2'
|\ | * (merge/2) Merge branch 'commits/6' | |\ * | \ Merge branch 'commits/5' |\ \ \ | * | | (commits/5) 5 | |/ / * | | Merge branch 'merge/1' |\ \ \ | * | | (merge/1) Merge branch 'commits/4' | |\| | | | * | (commits/4) 4 * | | | (commits/3) 3 |/ / / | | | * (commits/10) 10 | | | * (commits/9) 9 | | | * (commits/8) 8 | | | * (commits/7) 7 | | |/ | | * (commits/6) 6 | |/ |/|* | (commits/2) 2
|/
* (commits/1) 1t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 504
Quoted 9 lines
t/t5324-split-commit-graph.sh +9
-0+ git commit-graph write --reachable --split=no-merge && + test-tool read-graph >output && + cat >expect <<-EOF && + header: 43475048 1 $(test_oid oid_version) 4 2 + num_commits: 3 + chunks: oid_fanout oid_lookup commit_metadata + EOF + test_cmp expect output && + git commit-graph verifyAll right, so here we check that we have layer without GDAT at the top, and we request not to merge layers thus new layer will be created, then the new layer also does not have GDAT chunk (and has 3 commits).
Quoted 2 lines
Minor nitpick: shouldn't those test be indented?
The tests look indented to me and git diff HEAD^ --check gives nothing.
Did you mean the lines enclosed by EOF delimiter?
Quoted 20 lines
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 512
Quoted 8 lines
t/t5324-split-commit-graph.sh +8
-0+' + +test_expect_success 'writes generation data chunk when commit-graph chain is replaced' ' + cd "$TRASH_DIRECTORY/mixed" && + git commit-graph write --reachable --split=replace && + test_path_is_file $graphdir/commit-graph-chain && + test_line_count = 1 $graphdir/commit-graph-chain && + verify_chain_files_exist $graphdir &&All right, this checks that we have split commit-graph chain that consist of a single layer, and that the commit-graph file for this single layer exists.
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 513
Quoted 1 line
t/t5324-split-commit-graph.sh +1
-0+ graph_read_expect 15 &&Shouldn't we use
test-tool read-graphto check whethergeneration_datachunk is present... ah, sorry, I have realized that after previous patchesgraph_read_expect 15implicitly checks the latter, because in its' use oftest-tool read-graphit does expectgeneration_datachunk.
Quoted 5 lines
So we use
test-tool read-graphmanually to check thatgeneration_datachunk is absent, and we usegraph_read_expectto check that it is present (and in both cases that the number of commits matches). I wonder if it would be possible to simplify that...
The problem here is graph_read_expect() as defined in t5324-split-commit-graph takes two parameters - number of commits and number of base graphs. If the number of base graphs is not passed to the function call, it's assumed to be zero. Using a default parameter is tricky - I can fix it by manually adding a zero to each of graph_read_expect() in an additional preparatory patch.
Any other suggestions are welcome too.
Quoted 15 lines
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 514
Quoted 2 lines
t/t5324-split-commit-graph.sh +1
-0+ git commit-graph verifyAll right.
Quoted 10 lines
t/t5324-split-commit-graph.sh +10
-0+' + +test_expect_success 'add one commit, write a tip graph' ' + cd "$TRASH_DIRECTORY/mixed" && + test_commit 11 && + git branch commits/11 && + git commit-graph write --reachable --split && + test_path_is_missing $infodir/commit-graph && + test_path_is_file $graphdir/commit-graph-chain && + ls $graphdir/graph-*.graph >graph-files &&
Quoted 8 lines
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 527
Quoted 3 lines
t/t5324-split-commit-graph.sh +3
-0+ test_line_count = 2 graph-files && + verify_chain_files_exist $graphdir +'What it is meant to test? That adding single-commit to a 15 commit commit-graph file in split mode does not result in layers merging, and actually adds a new layer: we check that we have exactly two layers and that they are all OK.
This test is meant to check writing to a split graph in "normal" conditions (i.e. all existing layers have generation data chunk). The above tests are special cases as they involve merging layers with mixed generation number versions.
Quoted 7 lines
We don't check here that the newly created top layer commit-graph does have GDAT chunk, as it should be if the top layer (in this case the only layer) has GDAT chunk.
t/t5324-split-commit-graph.sh test_expect_success '--split=replace with partial Bloom data' ' · line 529
Quoted 2 lines
t/t5324-split-commit-graph.sh +1
-0+ test_done
Quoted 7 lines
One test we are missing is testing that merging layers is done correctly, namely that if we are merging layers in split commit-graph file, and the layer below the ones we are merging lacks GDAT chunk, then the result of the merge should also be without GDAT chunk. This would require at least two GDAT-less layers in a setup.
I'm not sure how difficult writing such test should be.
It wouldn't be too hard.
After the last test, I can write some more commits and write split commit-graph file without GDAT chunk. Then write some more commits and merge layers using git commit-graph write --max-commits=<nr>.
Thanks for pointing this out!
Quoted 4 lines
Best,
Signature · 1 line
Jakub Narębski
Thanks
- Abhishek
Re: [PATCH v4 08/10] commit-graph: use generation v2 only if entire chain does
Quoted 3 lines · Abhishek Kumar
Quoted 2 lines · Jakub Narębski
Quoted 1 line · Abhishek Kumar via GitGitGadget
From: Abhishek Kumar <[email protected]>
[...]
Quoted 59 lines
Quoted 49 lines
Quoted 7 lines
It is difficult to expose this issue in a test. Since we _start_ with artificially low generation numbers, any commit walk that prioritizes generation numbers will walk all of the commits with high generation number before walking the commits with low generation number. In all the cases I tried, the commit-graph layers themselves "protect" any incorrect behavior since none of the commits in the lower layer can reach the commits in the upper layer.
I don't quite understand the issue here. Unless none of the following query commands short-circuit and all walk the commit graph regardless of what generation numbers tell them, they should give different results with and without the commit graph, if we take two commits one from lower layer of split commit graph with GDAT, and one commit from the higher layer without GDAT, one lower reachable from the other higher.
We have the following query commands that we can check:
$ git merge-base --is-ancestor <lower> <higher> $ git merge-base --independent <lower> <higher>$ git tag --contains <tag-to-lower> $ git tag --merged <tag-to-higher> $ git branch --contains <branch-to-lower> $ git branch --merged <branch-to-higher>The second set of queries require for those commits to be tagged, or have branch pointing at them, respectively.
Also, shouldn't
git commit-graph verifyfail with split commit graph where the top layer is created with GIT_TEST_COMMIT_GRAPH_NO_GDAT=1?Let's assume that we have the following history, with newer commits shown on top like in
git log --graph --oneline --all:topological corrected generation level commit date number^*d 3 3
|c | 3 3 | | without GDAT ..|..|.....[layer.boundary]........................................ | | with GDAT | b 2 1112912113 1112912113 | | a | 2 1112912053 1112912053| /
|/
r 1 1112911993 1112911993Quoted 9 lines
*) each layer inspected individually.
With such history, we can for example reach 'a' from 'c', thus
git merge-base --is-ancestor a bshould return true value, but without this commit gen(a) > gen(c), instead of gen(a) <= gen(c); I use here weaker reachability condition, but the one that works also for commits outside the commit-graph (and those for which generation numbers overflows).
Quoted 8 lines
The original explanation was given by Dr. Stolee and he might not have thought exhaustively about the issue.
In any case, your explanation and the history make sense to me. I will try to add test and report back to the mailing list if something goes wrong.
Thank you for clarifying in such detail.
I don't think you need to add any new test. It should be enough to check that the first test introduced in this patch, namely 'setup repo for mixed generation commit-graph-chain', fails without the change in this patch -- as I think it does. This is because git commit-graph verify should fail with mixed-version split commit-graph with GDAT-less layer on top without this change.
Reporting this (possibly as from one sentence to one paragraph in the commit message) would be enough, in my opinion.
[...]
Quoted 14 lines
Quoted 9 lines
NOTE that this means that
GIT_TEST_COMMIT_GRAPH_NO_GDATprevents from writing GDAT chunk with generation data v2 unless we are merging layers, or replacing all of them with a single layer: then it is _ignored_.Should we clarify this fact in the description of
GIT_TEST_COMMIT_GRAPH_NO_GDATin t/README? Currently it reads:GIT_TEST_COMMIT_GRAPH_NO_GDAT=<boolean>, when true, forces the commit-graph to be written without generation data chunk.
I think it's better to *not* write generation data chunk if
GIT_TEST_COMMIT_GRAPH_NO_GDATis set even though all GDAT-less layers are merged, that is:
Quoted 8 lines
if (!ctx->write_generation_data && g->chunk_generation_data && !git_env_bool(GIT_TEST_COMMIT_GRAPH_NO_GDAT, 0)) ctx->write_generation_data = 1;With this change, we would have a method to force-write commit-graph without generation data chunk regardless of the shape of split commit-graph files.
While it would be more consistent to always behave like the old Git with GIT_TEST_COMMIT_GRAPH_NO_GDAT=1, it is in my opinion not necessary.
The only thing we need to test the mixed-version commit-graph chain is the ability to add new layer on top without GDAT. It does not matter if this layer is created from new commits or a result of partial or full merge of layers.
So the alternative to extending what GIT_TEST_COMMIT_GRAPH_NO_GDAT does that you propose here would be simply improving the description of t in t/README, e.g.
GIT_TEST_COMMIT_GRAPH_NO_GDAT=<boolean>, when true, forces the commit-graph, or new layer in split commit-graph chain, to be written without generation data chunk. It does not affect merging of layers.
For me either solution is fine.
[...]
Quoted 15 lines
Quoted 15 lines
commit-graph.h struct commit_graph { · line 68
Quoted 10 lines
commit-graph.h +1
-0@@ -64,6 +64,7 @@ struct commit_graph {struct object_directory *odb;uint32_t num_commits_in_base; +unsigned int read_generation_data;struct commit_graph *base_graph;All right, this new field is here to propagate to each layer the information whether we can read from the generation number v2 data chunk.
Quoted 6 lines
Quoted 3 lines
Though I am not sure whether this field should be added here, and whether it should be
unsigned int(we don't have to be that careful about saving space for this type).I cannot think of a more appropriate struct than
struct commit_graph. Any particular suggestions?
After thinking about it a bit more, I think it is fine to have it here in struct commit_graph, it is better than using a global variable (which would make code non-reentrant; not that we use multiple threads for reading multiple layers of the commit graph, but we might want to in the future).
[...]
Quoted 10 lines
Quoted 10 lines
commit-graph.h struct commit_graph { · line 69
Quoted 1 line
commit-graph.h +1
-0+ cd "$TRASH_DIRECTORY/mixed" &&The t/README says:
- Don't chdir around in tests. It is not sufficient to chdir to somewhere and then chdir back to the original location later in the test, as any intermediate step can fail and abort the test, causing the next test to start in an unexpected directory. Do so inside a subshell if necessary.
Quoted 4 lines
Quoted 1 line
Though I am not sure if it should apply also to this situation.
While I cannot avoid changing directory, using a subshell would be best to avoid causing the later tests to start in unexpected directories.
This would allow for easier skipping of tests, and failed tests would not propagate the error (because of subsequent tests after a failed one starting in unexpected directory).
[...]
Quoted 22 lines
Quoted 13 lines
Quoted 13 lines
commit-graph.h +13
-0+ for i in $(test_seq 3) + do + test_commit $i && + git branch commits/$i || return 1 + done && + git reset --hard commits/1 && + for i in $(test_seq 4 5) + do + test_commit $i && + git branch commits/$i || return 1 + done && + git reset --hard commits/2 && + for i in $(test_seq 6 10)Quoted 8 lines
commit-graph.h
Quoted 5 lines
commit-graph.h +5
-0+ do + test_commit $i && + git branch commits/$i || return 1 + done && + git commit-graph write --reachable --split &&Is there a reason why we do not check just written commit-graph file with
test-tool read-graph >output-layer-1?
Quoted 2 lines
We could check the written commit-graph file at this point but it's same as existing tests as above.
All right, thanks for an explanation.
Quoted 10 lines
Quoted 6 lines
commit-graph.h
Quoted 3 lines
commit-graph.h +2
-0+ git reset --hard commits/2 && + git merge commits/4 &&Shouldn't we use
test_mergeinstead ofgit merge; I am not sure when to use one or the other?
test_mergeis used in 26 places whereasgit mergeis used in over a thousand places.test_mergeis just not widely adopted and this lack of adoption prevents further use.
All right then.
Quoted 44 lines
Quoted 44 lines
commit-graph.h
Quoted 4 lines
commit-graph.h +4
-0+ git branch merge/1 && + git reset --hard commits/4 && + git merge commits/6 && + git branch merge/2 &&It would be nice to have ASCII-art of the history (of the graph of revisions) created here for subsequent tests:
/- 6 <-- 7 <-- 8 <-- 9 <-- 10* / \-\ / \ 1 <-- 2 <-- 3* \--\ | \ \ | \-----\ \ \ \ \ \-- 4*<------ M/1 M/2 |\ / | \-- 5* / \ / \------------/* - 1st layer
Though I am not sure if what I have created is readable; I think a better way to draw this graph is possible, for example:
/- 3* / / 1 <------ 2 <---- 6 <-- 7 <-- 8 <-- 9 <-- 10* \ \ \ \ \ \ \ \ \ \- 4* <-- M/1 \ |\ \ | \------------- M/2 \ \---- 5*Edit: as I see the history gets even more complicated, so perhaps ASCII-art diagram of the history with layers marked would be too complicated, and wouldn't bring much.
Quoted 5 lines
Quoted 1 line
Why do we need such shape of the history in the repository?
We don't need such a complicated shape. Any commit-graph file with 2-3 layers regardless of how commits are related should suffice. Will simplify.
If you are unsire if we need this shape of history to properly test all corner cases of the algorithm, or whether simple history would be enough, you can simply compare code coverage. Git Makefile ha the 'coverage' target (which requires 'gcov' tool).
NOTE: if it is possible to run 'make coverage' for you, it can be used to check if there are any parts of the new code that are not tested.
[...]
Quoted 14 lines
Quoted 14 lines
commit-graph.h
Quoted 9 lines
commit-graph.h +9
-0+ git commit-graph write --reachable --split=no-merge && + test-tool read-graph >output && + cat >expect <<-EOF && + header: 43475048 1 $(test_oid oid_version) 4 2 + num_commits: 3 + chunks: oid_fanout oid_lookup commit_metadata + EOF + test_cmp expect output && + git commit-graph verifyAll right, so here we check that we have layer without GDAT at the top, and we request not to merge layers thus new layer will be created, then the new layer also does not have GDAT chunk (and has 3 commits).
Quoted 6 lines
Quoted 2 lines
Minor nitpick: shouldn't those test be indented?
The tests look indented to me and
git diff HEAD^ --checkgives nothing.Did you mean the lines enclosed by EOF delimiter?
I'm sorry, that was my mistake -- tabs are used for indent, and the tabstop (in my newsreader) when being quoted made it look like it was not indented.
[...]
Quoted 18 lines
Quoted 18 lines
commit-graph.h
Quoted 6 lines
commit-graph.h +6
-0+test_expect_success 'writes generation data chunk when commit-graph chain is replaced' ' + cd"$TRASH_DIRECTORY/mixed" && + git commit-graph write --reachable --split=replace && + test_path_is_file $graphdir/commit-graph-chain && + test_line_count = 1 $graphdir/commit-graph-chain && + verify_chain_files_exist $graphdir &&All right, this checks that we have split commit-graph chain that consist of a single layer, and that the commit-graph file for this single layer exists.
commit-graph.h
Quoted 1 line
commit-graph.h +1
-0+ graph_read_expect 15 &&Shouldn't we use
test-tool read-graphto check whethergeneration_datachunk is present... ah, sorry, I have realized that after previous patchesgraph_read_expect 15implicitly checks the latter, because in its' use oftest-tool read-graphit does expectgeneration_datachunk.
Quoted 4 lines
Quoted 4 lines
So we use
test-tool read-graphmanually to check thatgeneration_datachunk is absent, and we usegraph_read_expectto check that it is present (and in both cases that the number of commits matches). I wonder if it would be possible to simplify that...
What I wanted to say that it might be better to have a second variant of graph_read_expect() for GDAT-less layers -- but this might be unnecessary complication.
Quoted 6 lines
The problem here is
graph_read_expect()as defined in t5324-split-commit-graph takes two parameters - number of commits and number of base graphs. If the number of base graphs is not passed to the function call, it's assumed to be zero. Using a default parameter is tricky - I can fix it by manually adding a zero to each ofgraph_read_expect()in an additional preparatory patch.
All right, thanks for an explanation. I should have examined graph_read_expect() in more detail.
Quoted 1 line
Any other suggestions are welcome too.
[...]
Quoted 17 lines
Quoted 8 lines
Quoted 8 lines
commit-graph.h +8
-0+test_expect_success 'add one commit, write a tip graph' ' + cd"$TRASH_DIRECTORY/mixed" && + test_commit 11 && + git branch commits/11 && + git commit-graph write --reachable --split && + test_path_is_missing $infodir/commit-graph && + test_path_is_file $graphdir/commit-graph-chain && + ls $graphdir/graph-*.graph >graph-files &&Quoted 8 lines
commit-graph.h
Quoted 3 lines
commit-graph.h +3
-0+ test_line_count = 2 graph-files && + verify_chain_files_exist $graphdir +'What it is meant to test? That adding single-commit to a 15 commit commit-graph file in split mode does not result in layers merging, and actually adds a new layer: we check that we have exactly two layers and that they are all OK.
Quoted 4 lines
This test is meant to check writing to a split graph in "normal" conditions (i.e. all existing layers have generation data chunk). The above tests are special cases as they involve merging layers with mixed generation number versions.
All right.
Quoted 15 lines
Quoted 7 lines
We don't check here that the newly created top layer commit-graph does have GDAT chunk, as it should be if the top layer (in this case the only layer) has GDAT chunk.
Quoted 2 lines
+
test_doneQuoted 7 lines
One test we are missing is testing that merging layers is done correctly, namely that if we are merging layers in split commit-graph file, and the layer below the ones we are merging lacks GDAT chunk, then the result of the merge should also be without GDAT chunk. This would require at least two GDAT-less layers in a setup.
I'm not sure how difficult writing such test should be.
Quoted 7 lines
It wouldn't be too hard.
After the last test, I can write some more commits and write split commit-graph file without GDAT chunk. Then write some more commits and merge layers using
git commit-graph write --max-commits=<nr>.Thanks for pointing this out!
Good.
Best,
Signature · 1 line
Jakub Narębski
PATCH v4 9/10 commit-reach: use corrected commit dates in paint_down_to_common()
In reply to Abhishek Kumar via GitGitGadgetFrom: Abhishek Kumar <[email protected]>
With corrected commit dates implemented, we no longer have to rely on commit date as a heuristic in paint_down_to_common().
While using corrected commit dates Git walks nearly the same number of commits as commit date, the process is slower as for each comparision we have to access a commit-slab (for corrected committer date) instead of accessing struct member (for committer date).
For example, the command git merge-base v4.8 v4.9 on the linux repository walks 167468 commits, taking 0.135s for committer date and 167496 commits, taking 0.157s for corrected committer date respectively.
t6404-recursive-merge setups a unique repository where all commits have the same committer date without well-defined merge-base.
While running tests with GIT_TEST_COMMIT_GRAPH unset, we use committer date as a heuristic in paint_down_to_common(). 6404.1 'combined merge conflicts' merges commits in the order:
- Merge C with B to form a intermediate commit.
- Merge the intermediate commit with A.
With GIT_TEST_COMMIT_GRAPH=1, we write a commit-graph and subsequently use the corrected committer date, which changes the order in which commits are merged:
- Merge A with B to form a intermediate commit.
- Merge the intermediate commit with C.
While resulting repositories are equivalent, 6404.4 'virtual trees were processed' fails with GIT_TEST_COMMIT_GRAPH=1 as we are selecting different merge-bases and thus have different object ids for the intermediate commits.
As this has already causes problems (as noted in 859fdc0 (commit-graph:
define GIT_TEST_COMMIT_GRAPH, 2018-08-29)), we disable commit graph within t6404-recursive-merge.
--- commit-graph.c | 14 ++++++++++++++ commit-graph.h | 8 +++++++- commit-reach.c | 2 +- t/t6404-recursive-merge.sh | 5 ++++- 4 files changed, 26 insertions(+), 3 deletions(-)
commit-graph.c +14 -0
@@ -705,6 +705,20 @@ int generation_numbers_enabled(struct repository *r)return !!first_generation; } +int corrected_commit_dates_enabled (struct repository *r) +{ +struct commit_graph *g; +if (!prepare_commit_graph (r)) +return 0 ; + + g = r->objects->commit_graph; + +if (!g->num_commits) +return 0 ; + +return g->read_generation_data; +} +struct bloom_filter_settings *get_bloom_filter_settings (struct repository *r) {struct commit_graph *g = r->objects->commit_graph;
commit-graph.h +7 -1
@@ -89,13 +89,19 @@ struct commit_graph *read_commit_graph_one(struct repository *r,struct commit_graph *parse_commit_graph (struct repository *r,void *graph_map,size_t graph_size); +struct bloom_filter_settings *get_bloom_filter_settings (struct repository *r); +/* * Return 1 if and only if the repository has a commit-graph * file and generation numbers are computed in that file. */ int generation_numbers_enabled (struct repository *r); -struct bloom_filter_settings *get_bloom_filter_settings (struct repository *r); +/* +* Return 1 if and only if the repository has a commit-graph +* file and generation data chunk has been written for the file. +*/ +int corrected_commit_dates_enabled (struct repository *r);enum commit_graph_write_flags {COMMIT_GRAPH_WRITE_APPEND = (1 <<0 ),
commit-reach.c +1 -1
@@ -39,7 +39,7 @@ static struct commit_list *paint_down_to_common(struct repository *r,int i;timestamp_t last_gen =GENERATION_NUMBER_INFINITY ; -if (!min_generation) +if (!min_generation && !corrected_commit_dates_enabled (r)) queue.compare = compare_commits_by_commit_date; one->object.flags |=PARENT1 ;
t/t6404-recursive-merge.sh +4 -1
@@ -15,6 +15,8 @@ GIT_COMMITTER_DATE="2006-12-12 23:28:00 +0100" export GIT_COMMITTER_DATE test_expect_success 'setup tests' ' + GIT_TEST_COMMIT_GRAPH=0 && + export GIT_TEST_COMMIT_GRAPH && echo 1 >a1 && git add a1 && GIT_AUTHOR_DATE="2006-12-12 23:00:00" git commit -m 1 a1 &&
@@ -66,7 +68,7 @@ test_expect_success 'setup tests' ' ' test_expect_success 'combined merge conflicts' ' - test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git merge -m final G + test_must_fail git merge -m final G ' test_expect_success 'result contains a conflict' '
@@ -82,6 +84,7 @@ test_expect_success 'result contains a conflict' ' ' test_expect_success 'virtual trees were processed' ' + # TODO: fragile test, relies on ambigious merge-base resolution git ls-files --stage >out && cat >expect <<-EOF &&
Signature · 1 line
gitgitgadget
Re: [PATCH v4 09/10] commit-reach: use corrected commit dates in paint_down_to_common()
Quoted 9 lines · Abhishek Kumar via GitGitGadget
From: Abhishek Kumar <[email protected]>
With corrected commit dates implemented, we no longer have to rely on commit date as a heuristic in
paint_down_to_common().While using corrected commit dates Git walks nearly the same number of commits as commit date, the process is slower as for each comparision we have to access a commit-slab (for corrected committer date) instead of accessing struct member (for committer date).
Something for the future: I wonder if it would be worth it to bring back generation number from the commit-slab into struct commit.
Quoted 4 lines
For example, the command
git merge-base v4.8 v4.9on the linux repository walks 167468 commits, taking 0.135s for committer date and 167496 commits, taking 0.157s for corrected committer date respectively.
I think it would be good idea to explicitly refer to the commit that changed paint_down_to_common() to *not* use generation numbers v1 (topological levels) in the cases such as this, namely 091f4cf3 (commit:
don't use generation numbers if not needed). In this commit we have the following:
This change makes a concrete difference depending on the topology of the commit graph. For instance, computing the merge-base between consecutive versions of the Linux kernel has no effect for versions after v4.9, but 'git merge-base v4.8 v4.9' presents a performance regression:
v2.18.0: 0.122s
v2.19.0-rc1: 0.547s
HEAD: 0.127s To determine that this was simply an ordering issue, I inserted a counter within the while loop of paint_down_to_common() and found that the loop runs 167,468 times in v2.18.0 and 635,579 times in v2.19.0-rc1.
The times you report (0.135s and 0.157s) are close to 0.122s / 0.127s reported in 091f4cf3 - that is most probably because of the differences in the system performance (hardware, operating system, load, etc.). Numbers of commits walked for the committed date heuristics, that is 167,468 agrees with your results; 167,496 (+28) for corrected commit date (generation number v2) is significantly smaller (-468,083) than 635,579 reported for topological levels (generation number v1).
I suspect that there are cases (with date skew) where corrected commit date gives better performance than committer date heuristics, and I am quite sure that generation number v2 can give better performance in case where paint_down_to_common() uses generation numbers.
.................................................................
Here begins separate second change, which is not put into separate commit because it is fairly tightly connected to the change described above. It would be good idea, in my opinion, to add a sentence that explicitely marks this switch, for example:
This change accidentally broke fragile t6404-recursive-merge test. t6404-recursive-merge setups a unique repository...
Maybe with s/accidentaly/incidentally/.
Or add some other way of connection those two parts of the commit messages.
Quoted 15 lines
t6404-recursive-merge setups a unique repository where all commits have the same committer date without well-defined merge-base.
While running tests with
GIT_TEST_COMMIT_GRAPHunset, we use committer date as a heuristic inpaint_down_to_common(). 6404.1 'combined merge conflicts' merges commits in the order:
- Merge C with B to form a intermediate commit.
- Merge the intermediate commit with A.With GIT_TEST_COMMIT_GRAPH=1, we write a commit-graph and subsequently use the corrected committer date, which changes the order in which commits are merged:
- Merge A with B to form a intermediate commit.
- Merge the intermediate commit with C.
Quoted 8 lines
While resulting repositories are equivalent, 6404.4 'virtual trees were processed' fails with GIT_TEST_COMMIT_GRAPH=1 as we are selecting different merge-bases and thus have different object ids for the intermediate commits.
As this has already causes problems (as noted in 859fdc0 (commit-graph:
defineGIT_TEST_COMMIT_GRAPH, 2018-08-29)), we disable commit graph within t6404-recursive-merge.
Very nice explanation.
Perhaps in the future we could make this test less fragile.
Quoted 20 lines
Signed-off-by Abhishek Kumar <[email protected]>--- commit-graph.c | 14 ++++++++++++++ commit-graph.h | 8 +++++++- commit-reach.c | 2 +- t/t6404-recursive-merge.sh | 5 ++++- 4 files changed, 26 insertions(+), 3 deletions(-)commit-graph.c +3
-0@@ -705,6 +705,20 @@ int generation_numbers_enabled(struct repository *r)return !!first_generation; } +int corrected_commit_dates_enabled (struct repository *r) +{ +struct commit_graph *g;
commit-graph.c int generation_numbers_enabled(struct repository *r) · line 720
Quoted 10 lines
commit-graph.c +10
-0+if (!prepare_commit_graph (r)) +return 0 ; + + g = r->objects->commit_graph; + +if (!g->num_commits) +return 0 ; + +return g->read_generation_data; +}
Very nice abstraction.
Minor issue: I wonder if it would be better to use _available() or "_present()" rather than _enabled() suffix.
Quoted 11 lines
commit-graph.c +1
-0+struct bloom_filter_settings *get_bloom_filter_settings (struct repository *r) {struct commit_graph *g = r->objects->commit_graph;commit-graph.h +0
-0@@ -89,13 +89,19 @@ struct commit_graph *read_commit_graph_one(struct repository *r,struct commit_graph *parse_commit_graph (struct repository *r,void *graph_map,size_t graph_size);
commit-graph.h struct commit_graph *read_commit_graph_one(struct repository *r, · line 98
Quoted 10 lines
commit-graph.h +2
-1+struct bloom_filter_settings *get_bloom_filter_settings (struct repository *r); +/* * Return 1 if and only if the repository has a commit-graph * file and generation numbers are computed in that file. */ int generation_numbers_enabled (struct repository *r); -struct bloom_filter_settings *get_bloom_filter_settings (struct repository *r);
This moving get_bloom_filter_settings() before generation_numbers_enabled() looks like accidental change. If not, why it is here?
commit-graph.h struct commit_graph *read_commit_graph_one(struct repository *r, · line 105
Quoted 6 lines
commit-graph.h +5
-0+/* +* Return 1 if and only if the repository has a commit-graph +* file and generation data chunk has been written for the file. +*/ +int corrected_commit_dates_enabled (struct repository *r);
All right, nice to have documentation for the public function.
Quoted 7 lines
commit-graph.h +0
-0enum commit_graph_write_flags {COMMIT_GRAPH_WRITE_APPEND = (1 <<0 ),commit-reach.c +0
-0
commit-reach.c static struct commit_list *paint_down_to_common(struct repository *r, · line 45
Quoted 8 lines
commit-reach.c +1
-1@@ -39,7 +39,7 @@ static struct commit_list *paint_down_to_common(struct repository *r,int i;timestamp_t last_gen =GENERATION_NUMBER_INFINITY ; -if (!min_generation) +if (!min_generation && !corrected_commit_dates_enabled (r)) queue.compare = compare_commits_by_commit_date; one->object.flags |=PARENT1 ;
All right, this is the meat of the first change.
t/t6404-recursive-merge.sh GIT_COMMITTER_DATE="2006-12-12 23:28:00 +0100" · line 22
Quoted 13 lines
t/t6404-recursive-merge.sh +2
-0@@ -15,6 +15,8 @@ GIT_COMMITTER_DATE="2006-12-12 23:28:00 +0100" export GIT_COMMITTER_DATE test_expect_success 'setup tests' ' + GIT_TEST_COMMIT_GRAPH=0 && + export GIT_TEST_COMMIT_GRAPH && echo 1 >a1 && git add a1 && GIT_AUTHOR_DATE="2006-12-12 23:00:00" git commit -m 1 a1 &&
All right, we turn off running this test with commit-graph for the whole script, not only for a single test. As this is a setup, it would be run even if we are skipping some tests.
t/t6404-recursive-merge.sh test_expect_success 'setup tests' ' · line 72
Quoted 7 lines
t/t6404-recursive-merge.sh +1
-1@@ -66,7 +68,7 @@ test_expect_success 'setup tests' ' ' test_expect_success 'combined merge conflicts' ' - test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git merge -m final G + test_must_fail git merge -m final G '
All right, it is no longer necessary to run this specific test with GIT_TEST_COMMIT_GRAPH=0 as now the whole script is run with this setting.
t/t6404-recursive-merge.sh test_expect_success 'result contains a conflict' ' · line 90
Quoted 10 lines
t/t6404-recursive-merge.sh +1
-0test_expect_success 'result contains a conflict' '@@ -82,6 +84,7 @@ test_expect_success 'result contains a conflict' ' ' test_expect_success 'virtual trees were processed' ' + # TODO: fragile test, relies on ambigious merge-base resolution git ls-files --stage >out && cat >expect <<-EOF &&
Good call! Nice adding TODO comment for the future.
Best,
Signature · 1 line
Jakub Narębski
Re: [PATCH v4 09/10] commit-reach: use corrected commit dates in paint_down_to_common()
Quoted 4 lines · [email protected] (Jakub Narębski) writes:
I suspect that there are cases (with date skew) where corrected commit date gives better performance than committer date heuristics, and I am quite sure that generation number v2 can give better performance in case where
paint_down_to_common()uses generation numbers.
Thanks for a well reasoned review.
Quoted 8 lines
.................................................................
Here begins separate second change, which is not put into separate commit because it is fairly tightly connected to the change described above. It would be good idea, in my opinion, to add a sentence that explicitely marks this switch, for example:
Quoted 4 lines
This change accidentally broke fragile t6404-recursive-merge test. t6404-recursive-merge setups a unique repository...
Maybe with s/accidentaly/incidentally/.
Also "setup" is not a verb. "... sets up a unique repository".
Quoted 10 lines
Or add some other way of connection those two parts of the commit messages.
...Quoted 3 lines
As this has already causes problems (as noted in 859fdc0 (commit-graph:
defineGIT_TEST_COMMIT_GRAPH, 2018-08-29)), we disable commit graph within t6404-recursive-merge.Very nice explanation.
Perhaps in the future we could make this test less fragile.
If "separate second change" is distracting, would it be an option to fix the test before this step, perhaps?
Thanks.
Re: [PATCH v4 09/10] commit-reach: use corrected commit dates in paint_down_to_common()
In reply to Jakub NarębskiQuoted 20 lines · Jakub Narębski
Quoted 9 lines · Abhishek Kumar via GitGitGadget
From: Abhishek Kumar <[email protected]>
With corrected commit dates implemented, we no longer have to rely on commit date as a heuristic in
paint_down_to_common().While using corrected commit dates Git walks nearly the same number of commits as commit date, the process is slower as for each comparision we have to access a commit-slab (for corrected committer date) instead of accessing struct member (for committer date).
Something for the future: I wonder if it would be worth it to bring back generation number from the commit-slab into
struct commit.Quoted 4 lines
For example, the command
git merge-base v4.8 v4.9on the linux repository walks 167468 commits, taking 0.135s for committer date and 167496 commits, taking 0.157s for corrected committer date respectively.
Quoted 7 lines
I think it would be good idea to explicitly refer to the commit that changed
paint_down_to_common()to *not* use generation numbers v1 (topological levels) in the cases such as this, namely091f4cf3(commit:
don't use generation numbers if not needed). In this commit we have the following:
...
I have re-arranged the first half of commit message:
091f4cf3 (commit: don't use generation numbers if not needed, 2018-08-30) changed paint_down_to_common() to use commit dates instead of generation numbers v1 (topological levels) as the performance regressed on certain topologies. With generation number v2 (corrected commit dates) implemented, we no longer have to rely on commit dates and can use generation numbers.
For example, the command git merge-base v4.8 v4.9 on the Linux repository walks 167468 commits, taking 0.135s for committer date and 167496 commits, taking 0.157s for corrected committer date respectively.
While using corrected commit dates Git walks nearly the same number of commits as commit date, the process is slower as for each comparision we have to access a commit-slab (for corrected committer date) instead of accessing struct member (for committer date).
Quoted 21 lines
The times you report (0.135s and 0.157s) are close to 0.122s / 0.127s reported in
091f4cf3- that is most probably because of the differences in the system performance (hardware, operating system, load, etc.). Numbers of commits walked for the committed date heuristics, that is 167,468 agrees with your results; 167,496 (+28) for corrected commit date (generation number v2) is significantly smaller (-468,083) than 635,579 reported for topological levels (generation number v1).I suspect that there are cases (with date skew) where corrected commit date gives better performance than committer date heuristics, and I am quite sure that generation number v2 can give better performance in case where
paint_down_to_common()uses generation numbers..................................................................
Here begins separate second change, which is not put into separate commit because it is fairly tightly connected to the change described above. It would be good idea, in my opinion, to add a sentence that explicitely marks this switch, for example:
Quoted 5 lines
This change accidentally broke fragile t6404-recursive-merge test. t6404-recursive-merge setups a unique repository...
Maybe with s/accidentaly/incidentally/.
Thanks, will add.
Quoted 20 lines
Or add some other way of connection those two parts of the commit messages.
...Quoted 4 lines
commit-graph.c +3
-0+int corrected_commit_dates_enabled (struct repository *r) +{ +struct commit_graph *g;commit-graph.c int generation_numbers_enabled(struct repository *r) · line 720
Quoted 10 lines
commit-graph.c +10
-0+if (!prepare_commit_graph (r)) +return 0 ; + + g = r->objects->commit_graph; + +if (!g->num_commits) +return 0 ; + +return g->read_generation_data; +}Very nice abstraction.
Quoted 3 lines
Minor issue: I wonder if it would be better to use
_available()or "_present()" rather than_enabled()suffix.
We could, but that breaks conformity with generation_numbers_enabled().
I see both functions to be similar in nature, to answer whether the commit-graph has X? X could be topological levels or corrected commit dates.
Quoted 20 lines
Quoted 20 lines
commit-graph.c +1
-0+struct bloom_filter_settings *get_bloom_filter_settings (struct repository *r) {struct commit_graph *g = r->objects->commit_graph;commit-graph.h +2
-0@@ -89,13 +89,19 @@ struct commit_graph *read_commit_graph_one(struct repository *r,struct commit_graph *parse_commit_graph (struct repository *r,void *graph_map,size_t graph_size); +struct bloom_filter_settings *get_bloom_filter_settings (struct repository *r); +/* * Return 1 if and only if the repository has a commit-graph * file and generation numbers are computed in that file. */ int generation_numbers_enabled (struct repository *r);
Quoted 4 lines
commit-graph.h struct commit_graph *read_commit_graph_one(struct repository *r, · line 98
Quoted 1 line
commit-graph.h +0
-1-struct bloom_filter_settings *get_bloom_filter_settings (struct repository *r);This moving
get_bloom_filter_settings()beforegeneration_numbers_enabled()looks like accidental change. If not, why it is here?
Right, that's an accidental change. I wanted to group generation_numbers_enabled() and corrected_commit_dates_enabled() together.
Quoted 6 lines
...
Best,
Signature · 1 line
Jakub Narębski
Thanks
- Abhishek
PATCH v4 10/10 doc: add corrected commit date info
In reply to Abhishek Kumar via GitGitGadgetFrom: Abhishek Kumar <[email protected]>
With generation data chunk and corrected commit dates implemented, let's update the technical documentation for commit-graph.
--- .../technical/commit-graph-format.txt | 21 +++++-- Documentation/technical/commit-graph.txt | 62 ++++++++++++++++--- 2 files changed, 69 insertions(+), 14 deletions(-)
Documentation/technical/commit-graph-format.txt +15 -6
@@ -4,11 +4,7 @@ Git commit graph format The Git commit graph stores a list of commit OIDs and some associated metadata, including: -- The generation number of the commit. Commits with no parents have - generation number 1; commits with parents have generation number - one more than the maximum generation number of its parents. We - reserve zero as special, and can be used to mark a generation - number invalid or as "not computed". +- The generation number of the commit. - The root tree OID.
@@ -86,13 +82,26 @@ CHUNK DATA: position. If there are more than two parents, the second value has its most-significant bit on and the other bits store an array position into the Extra Edge List chunk. - * The next 8 bytes store the generation number of the commit and + * The next 8 bytes store the topological level (generation number v1) + of the commit and the commit time in seconds since EPOCH. The generation number uses the higher 30 bits of the first 4 bytes, while the commit time uses the 32 bits of the second 4 bytes, along with the lowest 2 bits of the lowest byte, storing the 33rd and 34th bit of the commit time. + Generation Data (ID: {'G', 'D', 'A', 'T' }) (N * 4 bytes) + * This list of 4-byte values store corrected commit date offsets for the + commits, arranged in the same order as commit data chunk. + * If the corrected commit date offset cannot be stored within 31 bits, + the value has its most-significant bit on and the other bits store + the position of corrected commit date into the Generation Data Overflow + chunk. + + Generation Data Overflow (ID: {'G', 'D', 'O', 'V' }) [Optional] + * This list of 8-byte values stores the corrected commit dates for commits + with corrected commit date offsets that cannot be stored within 31 bits. + Extra Edge List (ID: {'E', 'D', 'G', 'E'}) [Optional] This list of 4-byte values store the second through nth parents for all octopus merges. The second parent value in the commit data stores
Documentation/technical/commit-graph.txt +54 -8
101 lines not on this page · raw message
Signature · 1 line
gitgitgadget
Re: [PATCH v4 10/10] doc: add corrected commit date info
Quoted 6 lines · Abhishek Kumar via GitGitGadget
From: Abhishek Kumar <[email protected]>
With generation data chunk and corrected commit dates implemented, let's update the technical documentation for commit-graph.
Signed-off-by Abhishek Kumar <[email protected]>
Nice.
Quoted 10 lines
--- .../technical/commit-graph-format.txt | 21 +++++-- Documentation/technical/commit-graph.txt | 62 ++++++++++++++++--- 2 files changed, 69 insertions(+), 14 deletions(-)Documentation/technical/commit-graph-format.txt +0
-0
Documentation/technical/commit-graph-format.txt Git commit graph format · line 7
Quoted 9 lines
Documentation/technical/commit-graph-format.txt +1
-5@@ -4,11 +4,7 @@ Git commit graph format The Git commit graph stores a list of commit OIDs and some associated metadata, including: -- The generation number of the commit. Commits with no parents have - generation number 1; commits with parents have generation number - one more than the maximum generation number of its parents. We - reserve zero as special, and can be used to mark a generation - number invalid or as "not computed". +- The generation number of the commit.
All right, because we could store both generation number v1 and generation number v2 in the commit-graph file, and we need to describe both, the description is now consolidated and in only one place.
Documentation/technical/commit-graph-format.txt CHUNK DATA: · line 86
Quoted 10 lines
Documentation/technical/commit-graph-format.txt +2
-1- The root tree OID.@@ -86,13 +82,26 @@ CHUNK DATA: position. If there are more than two parents, the second value has its most-significant bit on and the other bits store an array position into the Extra Edge List chunk. - * The next 8 bytes store the generation number of the commit and + * The next 8 bytes store the topological level (generation number v1) + of the commit and
All right, this is updated information about CDAT chunk.
Documentation/technical/commit-graph-format.txt CHUNK DATA: · line 93
Quoted 7 lines
Documentation/technical/commit-graph-format.txt +1
-0the commit time in seconds since EPOCH. The generation number uses the higher 30 bits of the first 4 bytes, while the commit time uses the 32 bits of the second 4 bytes, along with the lowest 2 bits of the lowest byte, storing the 33rd and 34th bit of the commit time. + Generation Data (ID: {'G', 'D', 'A', 'T' }) (N * 4 bytes)
Should we mark this chunk as "[Optional]"? Its absence is not an error.
Documentation/technical/commit-graph-format.txt CHUNK DATA: · line 99
Quoted 6 lines
Documentation/technical/commit-graph-format.txt +6
-0+ * This list of 4-byte values store corrected commit date offsets for the + commits, arranged in the same order as commit data chunk. + * If the corrected commit date offset cannot be stored within 31 bits, + the value has its most-significant bit on and the other bits store + the position of corrected commit date into the Generation Data Overflow + chunk.
All right.
Documentation/technical/commit-graph-format.txt CHUNK DATA: · line 103
Quoted 4 lines
Documentation/technical/commit-graph-format.txt +4
-0+ + Generation Data Overflow (ID: {'G', 'D', 'O', 'V' }) [Optional] + * This list of 8-byte values stores the corrected commit dates for commits + with corrected commit date offsets that cannot be stored within 31 bits.
A question: do we store 8-byte / 64-bit corrected commit date *directly*, or do we store corrected commit date *offset* as 8-byte / 64-bit value?
Perhaps we should add the information that [like the EDGE chunk] it is present only when necessary, and that it is present only when GDAT chunk is present (it might be obvious, but it could be better to state this explicitly).
Documentation/technical/commit-graph-format.txt CHUNK DATA: · line 104
Quoted 1 line
Documentation/technical/commit-graph-format.txt +1
-0+
All right, this is the information about two new chunks (with the mentioned above caveat about the clarity of the description of overflow-handling chunk).
Quoted 8 lines
Documentation/technical/commit-graph-format.txt +0
-0Extra Edge List (ID: {'E', 'D', 'G', 'E'}) [Optional] This list of 4-byte values store the second through nth parents for all octopus merges. The second parent value in the commit data storesDocumentation/technical/commit-graph.txt +0
-0
Documentation/technical/commit-graph.txt A consumer may load the following info for a commit from the graph: · line 43
Quoted 7 lines
Documentation/technical/commit-graph.txt +3
-1@@ -38,14 +38,31 @@ A consumer may load the following info for a commit from the graph: Values 1-4 satisfy the requirements of parse_commit_gently(). -Define the "generation number" of a commit recursively as follows: +There are two definitions of generation number: +1. Corrected committer dates (generation number v2) +2. Topological levels (generation nummber v1)
All right.
Documentation/technical/commit-graph.txt A consumer may load the following info for a commit from the graph: · line 48
Quoted 8 lines
Documentation/technical/commit-graph.txt +3
-3- * A commit with no parents (a root commit) has generation number one. +Define "corrected committer date" of a commit recursively as follows: - * A commit with at least one parent has generation number one more than - the largest generation number among its parents. + * A commit with no parents (a root commit) has corrected committer date + equal to its committer date.
Minor nitpick: the above point has been accidentally indented one space more than necessary, and than is indented in other places. Or maybe that fixes / unifies the formatting... I am not sure.
Documentation/technical/commit-graph.txt A consumer may load the following info for a commit from the graph: · line 56
Quoted 9 lines
Documentation/technical/commit-graph.txt +7
-1-Equivalently, the generation number of a commit A is one more than the + * A commit with at least one parent has corrected committer date equal to + the maximum of its commiter date and one more than the largest corrected + committer date among its parents. + + * As a special case, a root commit with timestamp zero has corrected commit + date of 1, to be able to distinguish it from GENERATION_NUMBER_ZERO + (that is, an uncomputed corrected commit date).
All right. Looks good.
Documentation/technical/commit-graph.txt A consumer may load the following info for a commit from the graph: · line 64
Quoted 8 lines
Documentation/technical/commit-graph.txt +8
-0+ +Define the "topological level" of a commit recursively as follows: + + * A commit with no parents (a root commit) has topological level of one. + + * A commit with at least one parent has topological level one more than + the largest topological level among its parents. +
All right, this just repeats what was written before, or in other words move existing contents lower/later, just with 'generation number' replaced by 'topological level' (though it might be not obvious from the patch because of the latter change).
Documentation/technical/commit-graph.txt is easier to use for computation and observing the following property: · line 82
Quoted 11 lines
Documentation/technical/commit-graph.txt +4
-0+Equivalently, the topological level of a commit A is one more than the length of a longest path from A to a root commit. The recursive definition is easier to use for computation and observing the following property:@@ -60,6 +77,9 @@ is easier to use for computation and observing the following property: generation numbers, then we always expand the boundary commit with highest generation number and can easily detect the stopping condition. +The properties applies to both versions of generation number, that is both +corrected committer dates and topological levels. +
I think it should be "This property" or "The property", not "The properties"; it is a single property, a single condition.
We can alternatively say "This condition is fulfilled by both versions...", or "This condition is true for both versions...".
Quoted 4 lines
Documentation/technical/commit-graph.txt +0
-0This property can be used to significantly reduce the time it takes to walk commits and determine topological relationships. Without generation numbers, the general heuristic is the following:
Documentation/technical/commit-graph.txt numbers, the general heuristic is the following: · line 95
Quoted 10 lines
Documentation/technical/commit-graph.txt +3
-1@@ -67,7 +87,9 @@ numbers, the general heuristic is the following: If A and B are commits with commit time X and Y, respectively, and X < Y, then A _probably_ cannot reach B. -This heuristic is currently used whenever the computation is allowed to +In absence of corrected commit dates (for example, old versions of Git or +mixed generation graph chains), +this heuristic is currently used whenever the computation is allowed to violate topological relationships due to clock skew (such as "git log" with default order), but is not used when the topological order is required (such as merge base calculations, "git log --graph").
All right, this explains when commit date heuristics is used (which is less often than before).
Documentation/technical/commit-graph.txt in the commit graph. We can treat these commits as having "infinite" · line 102
Quoted 6 lines
Documentation/technical/commit-graph.txt +1
-1@@ -77,7 +99,7 @@ in the commit graph. We can treat these commits as having "infinite" generation number and walk until reaching commits with known generation number. -We use the macro GENERATION_NUMBER_INFINITY = 0xFFFFFFFF to mark commits not +We use the macro GENERATION_NUMBER_INFINITY to mark commits not
All right, 64-bit GENERATION_NUMBER_INFINITY = 0xFFFFFFFFFFFFFFFF is a bit unwieldy...
Documentation/technical/commit-graph.txt fully-computed generation numbers. Using strict inequality may result in · line 118
Quoted 9 lines
Documentation/technical/commit-graph.txt +1
-1in the commit-graph file. If a commit-graph file was written by a version of Git that did not compute generation numbers, then those commits will have generation number represented by the macro GENERATION_NUMBER_ZERO = 0.@@ -93,7 +115,7 @@ fully-computed generation numbers. Using strict inequality may result in walking a few extra commits, but the simplicity in dealing with commits with generation number *_INFINITY or *_ZERO is valuable. -We use the macro GENERATION_NUMBER_MAX = 0x3FFFFFFF to for commits whose +We use the macro GENERATION_NUMBER_MAX for commits whose
This should be
+We use the macro GENERATION_NUMBER_V1_MAX = 0x3FFFFFFF to for commits whose +topological levels (generation number v1) are computed to be at least this value. We limit at
this value since it is the largest value that can be stored in the
+commit-graph file using the 30 bits available to topological levels. This
We need to use "topological levels" or "generation numbers v1" thorough the rest of this section.
Documentation/technical/commit-graph.txt The merge strategy values (2 for the size multiple, 64,000 for the maximum · line 291
Quoted 7 lines
Documentation/technical/commit-graph.txt +0
-0generation numbers are computed to be at least this value. We limit at this value since it is the largest value that can be stored in the commit-graph file using the 30 bits available to generation numbers. This@@ -267,6 +289,30 @@ The merge strategy values (2 for the size multiple, 64,000 for the maximum number of commits) could be extracted into config settings for full flexibility.
All right, I agree that we don't need to write about overflow handling for storing corrected committer dates (generation number v2) as offsets; this is something format-specific, and this documentation is more about using commit-graph data. What is present in commit-graph-format.txt should be enough information.
Sidenote: I wonder if other Git implementations such as JGit, Dulwich, Gitoxide (gix), go-git have support for the commit-graph file...
Quoted 4 lines
Documentation/technical/commit-graph.txt +4
-0+## Handling Mixed Generation Number Chains + +With the introduction of generation number v2 and generation data chunk, the +following scenario is possible:
Documentation/technical/commit-graph.txt The merge strategy values (2 for the size multiple, 64,000 for the maximum · line 305
Quoted 10 lines
Documentation/technical/commit-graph.txt +10
-0+ +1. "New" Git writes a commit-graph with the corrected commit dates. +2. "Old" Git writes a split commit-graph on top without corrected commit dates. + +A naive approach of using the newest available generation number from +each layer would lead to violated expectations: the lower layer would +use corrected commit dates which are much larger than the topological +levels of the higher layer. For this reason, Git inspects each layer to +see if any layer is missing corrected commit dates. In such a case, Git +only uses topological level
This should end in full stop:
+only uses topological levels.
Or maybe we should expand the last sentence a bit:
+only uses topological levels for generation numbers.
Sidenote: it is a good explanation, even if Git can make use of the property described below that only topmost layers might be missing corrected commit graph by the construction (so it needs to check only the top layer).
Documentation/technical/commit-graph.txt The merge strategy values (2 for the size multiple, 64,000 for the maximum · line 315
Quoted 10 lines
Documentation/technical/commit-graph.txt +10
-0+ +When writing a new layer in split commit-graph, we write corrected commit +dates if the topmost layer has corrected commit dates written. This +guarantees that if a layer has corrected commit dates, all lower layers +must have corrected commit dates as well. + +When merging layers, we do not consider whether the merged layers had corrected +commit dates. Instead, the new layer will have corrected commit dates if and +only if all existing layers below the new layer have corrected commit dates. +
Perhaps we should explicitly say that when rewriting split commit-graph as a single file (--split=replace) then the newly created single layer would store corrected commit dates.
Documentation/technical/commit-graph.txt The merge strategy values (2 for the size multiple, 64,000 for the maximum · line 318
Quoted 3 lines
Documentation/technical/commit-graph.txt +0
-0## Deleting graph-{hash} files After a new tip file is written, some `graph-{hash}` files may no longer
Best,
Signature · 1 line
Jakub Narębski
Re: [PATCH v4 10/10] doc: add corrected commit date info
Quoted 54 lines · Jakub Narębski
Quoted 6 lines · Abhishek Kumar via GitGitGadget
From: Abhishek Kumar <[email protected]>
With generation data chunk and corrected commit dates implemented, let's update the technical documentation for commit-graph.
Signed-off-by Abhishek Kumar <[email protected]>Nice.
Quoted 10 lines
--- .../technical/commit-graph-format.txt | 21 +++++-- Documentation/technical/commit-graph.txt | 62 ++++++++++++++++--- 2 files changed, 69 insertions(+), 14 deletions(-)Documentation/technical/commit-graph-format.txt +0
-0Documentation/technical/commit-graph-format.txt Git commit graph format · line 7
Quoted 9 lines
Documentation/technical/commit-graph-format.txt +1
-5@@ -4,11 +4,7 @@ Git commit graph format The Git commit graph stores a list of commit OIDs and some associated metadata, including: -- The generation number of the commit. Commits with no parents have - generation number 1; commits with parents have generation number - one more than the maximum generation number of its parents. We - reserve zero as special, and can be used to mark a generation - number invalid or as "not computed". +- The generation number of the commit.All right, because we could store both generation number v1 and generation number v2 in the commit-graph file, and we need to describe both, the description is now consolidated and in only one place.
Documentation/technical/commit-graph-format.txt CHUNK DATA: · line 86
Quoted 10 lines
Documentation/technical/commit-graph-format.txt +2
-1- The root tree OID.@@ -86,13 +82,26 @@ CHUNK DATA: position. If there are more than two parents, the second value has its most-significant bit on and the other bits store an array position into the Extra Edge List chunk. - * The next 8 bytes store the generation number of the commit and + * The next 8 bytes store the topological level (generation number v1) + of the commit andAll right, this is updated information about CDAT chunk.
Quoted 6 lines
Documentation/technical/commit-graph-format.txt +0
-0the commit time in seconds since EPOCH. The generation number uses the higher 30 bits of the first 4 bytes, while the commit time uses the 32 bits of the second 4 bytes, along with the lowest 2 bits of the lowest byte, storing the 33rd and 34th bit of the commit time.
Quoted 3 lines
Documentation/technical/commit-graph-format.txt CHUNK DATA: · line 93
Quoted 1 line
Documentation/technical/commit-graph-format.txt +1
-0+ Generation Data (ID: {'G', 'D', 'A', 'T' }) (N * 4 bytes)Should we mark this chunk as "[Optional]"? Its absence is not an error.
I think we should mark it as "optional", although optional might not have been the best choice word.
Optional (for me) implies that it is configurable and decided by the end-user directly. However, it is *conditional* - on the existing commit graph file(s) (if any) and the version of Git.
Quoted 9 lines
Documentation/technical/commit-graph-format.txt CHUNK DATA: · line 99
Quoted 6 lines
Documentation/technical/commit-graph-format.txt +6
-0+ * This list of 4-byte values store corrected commit date offsets for the + commits, arranged in the same order as commit data chunk. + * If the corrected commit date offset cannot be stored within 31 bits, + the value has its most-significant bit on and the other bits store + the position of corrected commit date into the Generation Data Overflow + chunk.All right.
Quoted 8 lines
Documentation/technical/commit-graph-format.txt CHUNK DATA: · line 103
Quoted 4 lines
Documentation/technical/commit-graph-format.txt +4
-0+ + Generation Data Overflow (ID: {'G', 'D', 'O', 'V' }) [Optional] + * This list of 8-byte values stores the corrected commit dates for commits + with corrected commit date offsets that cannot be stored within 31 bits.A question: do we store 8-byte / 64-bit corrected commit date *directly*, or do we store corrected commit date *offset* as 8-byte / 64-bit value?
We store the dates directly rather 8-byte offsets. Will clarify.
Quoted 5 lines
Perhaps we should add the information that [like the EDGE chunk] it is present only when necessary, and that it is present only when GDAT chunk is present (it might be obvious, but it could be better to state this explicitly).
It's always better to be explicit. Thanks for the detailed review.
Quoted 28 lines
Documentation/technical/commit-graph-format.txt CHUNK DATA: · line 104
Quoted 1 line
Documentation/technical/commit-graph-format.txt +1
-0+All right, this is the information about two new chunks (with the mentioned above caveat about the clarity of the description of overflow-handling chunk).
Quoted 8 lines
Documentation/technical/commit-graph-format.txt +0
-0Extra Edge List (ID: {'E', 'D', 'G', 'E'}) [Optional] This list of 4-byte values store the second through nth parents for all octopus merges. The second parent value in the commit data storesDocumentation/technical/commit-graph.txt +0
-0Documentation/technical/commit-graph.txt A consumer may load the following info for a commit from the graph: · line 43
Quoted 7 lines
Documentation/technical/commit-graph.txt +3
-1@@ -38,14 +38,31 @@ A consumer may load the following info for a commit from the graph: Values 1-4 satisfy the requirements of parse_commit_gently(). -Define the "generation number" of a commit recursively as follows: +There are two definitions of generation number: +1. Corrected committer dates (generation number v2) +2. Topological levels (generation nummber v1)All right.
Quoted 4 lines
Documentation/technical/commit-graph.txt +1
-1- * A commit with no parents (a root commit) has generation number one. +Define "corrected committer date" of a commit recursively as follows:
Quoted 9 lines
Documentation/technical/commit-graph.txt A consumer may load the following info for a commit from the graph: · line 48
Quoted 4 lines
Documentation/technical/commit-graph.txt +2
-2- * A commit with at least one parent has generation number one more than - the largest generation number among its parents. + * A commit with no parents (a root commit) has corrected committer date + equal to its committer date.Minor nitpick: the above point has been accidentally indented one space more than necessary, and than is indented in other places. Or maybe that fixes / unifies the formatting... I am not sure.
That's a force of habit - I like to write markdown with greater indentation. Should have been indented with one space instead of two.
Quoted 137 lines
Documentation/technical/commit-graph.txt A consumer may load the following info for a commit from the graph: · line 56
Quoted 9 lines
Documentation/technical/commit-graph.txt +7
-1-Equivalently, the generation number of a commit A is one more than the + * A commit with at least one parent has corrected committer date equal to + the maximum of its commiter date and one more than the largest corrected + committer date among its parents. + + * As a special case, a root commit with timestamp zero has corrected commit + date of 1, to be able to distinguish it from GENERATION_NUMBER_ZERO + (that is, an uncomputed corrected commit date).All right. Looks good.
Documentation/technical/commit-graph.txt A consumer may load the following info for a commit from the graph: · line 64
Quoted 8 lines
Documentation/technical/commit-graph.txt +8
-0+ +Define the "topological level" of a commit recursively as follows: + + * A commit with no parents (a root commit) has topological level of one. + + * A commit with at least one parent has topological level one more than + the largest topological level among its parents. +All right, this just repeats what was written before, or in other words move existing contents lower/later, just with 'generation number' replaced by 'topological level' (though it might be not obvious from the patch because of the latter change).
Documentation/technical/commit-graph.txt is easier to use for computation and observing the following property: · line 82
Quoted 11 lines
Documentation/technical/commit-graph.txt +4
-0+Equivalently, the topological level of a commit A is one more than the length of a longest path from A to a root commit. The recursive definition is easier to use for computation and observing the following property:@@ -60,6 +77,9 @@ is easier to use for computation and observing the following property: generation numbers, then we always expand the boundary commit with highest generation number and can easily detect the stopping condition. +The properties applies to both versions of generation number, that is both +corrected committer dates and topological levels. +I think it should be "This property" or "The property", not "The properties"; it is a single property, a single condition.
We can alternatively say "This condition is fulfilled by both versions...", or "This condition is true for both versions...".
Quoted 4 lines
Documentation/technical/commit-graph.txt +0
-0This property can be used to significantly reduce the time it takes to walk commits and determine topological relationships. Without generation numbers, the general heuristic is the following:Documentation/technical/commit-graph.txt numbers, the general heuristic is the following: · line 95
Quoted 10 lines
Documentation/technical/commit-graph.txt +3
-1@@ -67,7 +87,9 @@ numbers, the general heuristic is the following: If A and B are commits with commit time X and Y, respectively, and X < Y, then A _probably_ cannot reach B. -This heuristic is currently used whenever the computation is allowed to +In absence of corrected commit dates (for example, old versions of Git or +mixed generation graph chains), +this heuristic is currently used whenever the computation is allowed to violate topological relationships due to clock skew (such as "git log" with default order), but is not used when the topological order is required (such as merge base calculations, "git log --graph").All right, this explains when commit date heuristics is used (which is less often than before).
Documentation/technical/commit-graph.txt in the commit graph. We can treat these commits as having "infinite" · line 102
Quoted 6 lines
Documentation/technical/commit-graph.txt +1
-1@@ -77,7 +99,7 @@ in the commit graph. We can treat these commits as having "infinite" generation number and walk until reaching commits with known generation number. -We use the macro GENERATION_NUMBER_INFINITY = 0xFFFFFFFF to mark commits not +We use the macro GENERATION_NUMBER_INFINITY to mark commits notAll right, 64-bit
GENERATION_NUMBER_INFINITY=0xFFFFFFFFFFFFFFFFis a bit unwieldy...Documentation/technical/commit-graph.txt fully-computed generation numbers. Using strict inequality may result in · line 118
Quoted 9 lines
Documentation/technical/commit-graph.txt +1
-1in the commit-graph file. If a commit-graph file was written by a version of Git that did not compute generation numbers, then those commits will have generation number represented by the macro GENERATION_NUMBER_ZERO = 0.@@ -93,7 +115,7 @@ fully-computed generation numbers. Using strict inequality may result in walking a few extra commits, but the simplicity in dealing with commits with generation number *_INFINITY or *_ZERO is valuable. -We use the macro GENERATION_NUMBER_MAX = 0x3FFFFFFF to for commits whose +We use the macro GENERATION_NUMBER_MAX for commits whoseThis should be
+We use the macro
GENERATION_NUMBER_V1_MAX=0x3FFFFFFFto for commits whose +topological levels (generation number v1) are computed to be at least this value. We limit at
this value since it is the largest value that can be stored in the
+commit-graph file using the 30 bits available to topological levels. ThisWe need to use "topological levels" or "generation numbers v1" thorough the rest of this section.
Documentation/technical/commit-graph.txt The merge strategy values (2 for the size multiple, 64,000 for the maximum · line 291
Quoted 7 lines
Documentation/technical/commit-graph.txt +0
-0generation numbers are computed to be at least this value. We limit at this value since it is the largest value that can be stored in the commit-graph file using the 30 bits available to generation numbers. This@@ -267,6 +289,30 @@ The merge strategy values (2 for the size multiple, 64,000 for the maximum number of commits) could be extracted into config settings for full flexibility.All right, I agree that we don't need to write about overflow handling for storing corrected committer dates (generation number v2) as offsets; this is something format-specific, and this documentation is more about using commit-graph data. What is present in commit-graph-format.txt should be enough information.
Sidenote: I wonder if other Git implementations such as JGit, Dulwich, Gitoxide (gix), go-git have support for the commit-graph file...
Quoted 4 lines
Documentation/technical/commit-graph.txt +4
-0+## Handling Mixed Generation Number Chains + +With the introduction of generation number v2 and generation data chunk, the +following scenario is possible:Documentation/technical/commit-graph.txt The merge strategy values (2 for the size multiple, 64,000 for the maximum · line 305
Quoted 10 lines
Documentation/technical/commit-graph.txt +10
-0+ +1. "New" Git writes a commit-graph with the corrected commit dates. +2. "Old" Git writes a split commit-graph on top without corrected commit dates. + +A naive approach of using the newest available generation number from +each layer would lead to violated expectations: the lower layer would +use corrected commit dates which are much larger than the topological +levels of the higher layer. For this reason, Git inspects each layer to +see if any layer is missing corrected commit dates. In such a case, Git +only uses topological levelThis should end in full stop:
+only uses topological levels.
Or maybe we should expand the last sentence a bit:
+only uses topological levels for generation numbers.
Sidenote: it is a good explanation, even if Git can make use of the property described below that only topmost layers might be missing corrected commit graph by the construction (so it needs to check only the top layer).
Quoted 15 lines
Documentation/technical/commit-graph.txt The merge strategy values (2 for the size multiple, 64,000 for the maximum · line 315
Quoted 10 lines
Documentation/technical/commit-graph.txt +10
-0+ +When writing a new layer in split commit-graph, we write corrected commit +dates if the topmost layer has corrected commit dates written. This +guarantees that if a layer has corrected commit dates, all lower layers +must have corrected commit dates as well. + +When merging layers, we do not consider whether the merged layers had corrected +commit dates. Instead, the new layer will have corrected commit dates if and +only if all existing layers below the new layer have corrected commit dates. +Perhaps we should explicitly say that when rewriting split commit-graph as a single file (
--split=replace) then the newly created single layer would store corrected commit dates.
Rewriting split commit-graph as a single file is a case where there are no "existing layers below the new layer". We should clarify that if the new layer is the only layer, it will always have corrected commit dates when written by compatible versions of Git.
I have appended a paragraph at the end:
While writing or merging layers, if the new layer is the only layer, it will have corrected commit dates when written by compatible versions of Git. Thus, rewriting split commit-graph as a singel file (--split=replace) creates a single layer with corrected commit dates.
Quoted 7 lines
Documentation/technical/commit-graph.txt The merge strategy values (2 for the size multiple, 64,000 for the maximum · line 318
Quoted 3 lines
Documentation/technical/commit-graph.txt +0
-0## Deleting graph-{hash} files After a new tip file is written, some `graph-{hash}` files may no longerBest,
Signature · 1 line
Jakub Narębski
Thanks
- Abhishek
Hi Abhishek,
Quoted 2 lines · Abhishek Kumar via GitGitGadget
This patch series implements the corrected commit date offsets as generation number v2, along with other pre-requisites.
Thanks a lot for continued working on this patch series.
Quoted 7 lines
Git uses topological levels in the commit-graph file for commit-graph traversal operations like git log --graph. Unfortunately, using topological levels can result in a worse performance than without them when compared with committer date as a heuristics. For example, git merge-base v4.8 v4.9 on the Linux repository walks 635,579 commits using topological levels and walks 167,468 using committer date.
Very minor nitpick: it would make it easier to read if the commands themself would be put inside single quotes or backticks, e.g. git log --graph and git merge-base v4.8 v4.9.
I wonder if it is worth mentioning (probably not) that this performance hit was the reason why since 091f4cf3 git merge-base uses committer date heuristics unless there is a cutoff and using topological levels (generation date v1) is expected to give better performance.
Quoted 4 lines
Thus, the need for generation number v2 was born. New generation number needed to provide good performance, increment updates, and backward compatibility. Due to an unfortunate problem 1
Minor issue: this looks a bit strange; is there an error in formatting this part?
Quoted 10 lines
[https://public-inbox.org/git/[email protected]/], we also needed a way to distinguish between the old and new generation number without incrementing graph version.
Various candidates were examined (https://github.com/derrickstolee/gen-test,
https://github.com/abhishekkumar2718/git/pull/1). The proposed generation number v2, Corrected Commit Date with Mononotically Increasing Offsets performed much worse than committer date (506,577 vs. 167,468 commits walked for git merge-base v4.8 v4.9) and was dropped.
Quoted 3 lines
Using Generation Data chunk (GDAT) relieves the requirement of backward compatibility as we would continue to store topological levels in Commit Data (CDAT) chunk.
Nice writeup about the history of generation number v2, much appreciated.
Quoted 2 lines
Thus, Corrected Commit Date was chosen as generation
number v2. The Corrected Commit Date is defined as:
Minor nitpick: it would be probably better to use "is defined as follows." instead of "is defined as:".
Quoted 7 lines
For a commit C, let its corrected commit date be the maximum of the commit date of C and the corrected commit dates of its parents plus 1. Then corrected commit date offset is the difference between corrected commit date of C and commit date of C. As a special case, a root commit with timestamp zero has corrected commit date of 1 to be able distinguish it from
GENERATION_NUMBER_ZERO(that is, an uncomputed corrected commit date).
Very minor nitpick: s/with timestamp/with *the* timestamp/, and s/to be able distinguish/to be able *to* distinguish/ (without the '*' used to mark the additions).
Quoted 2 lines
We will introduce an additional commit-graph chunk, Generation Data chunk,
Or "Generation DATa chunk", if we want to emphasize where its name came from, or even "Generation DATa (GDAT) chunk". But it is fine as it is now, though it would be good idea to write "Generation Data (GDAT) chunk" to explicitly state its name / shortcut.
Quoted 5 lines
and store corrected commit date offsets in GDAT chunk while storing topological levels in CDAT chunk. The old versions of Git would ignore GDAT chunk, using topological levels from CDAT chunk. In contrast, new versions of Git would use corrected commit dates, falling back to topological level if the generation data chunk is absent in the commit-graph file.
Nice writeup of handling the backward compatibility.
Quoted 8 lines
While storing corrected commit date offsets saves us 4 bytes per commit (as compared with storing corrected commit dates directly), it's possible for the offset to overflow the space allocated. To handle such cases, we introduce a new chunk, Generation Data Overflow (GDOV) that stores the corrected commit date. For overflowing offsets, we set MSB and store the position into the GDOV chunk, in a mechanism similar to the Extra Edges list chunk.
Very minor suggestion: perhaps it would be better to use "it's however possible".
Very minor suggestion: "it's possible for the offset to overflow" could be simplified to just "the offset can overflow"... though the simplified version loses a bit of hint that the overflow should be very rare in real repositories.
But it is just fine as it is now; I am not a native English speaker to judge which version is better.
Quoted 16 lines
For mixed generation number environment (for example new Git on the command line, old Git used by GUI client), we can encounter a mixed-chain commit-graph (a commit-graph chain where some of split commit-graph files have GDAT chunk and others do not). As backward compatibility is one of the goals, we can define the following behavior:
While reading a mixed-chain commit-graph version, we fall back on topological levels as corrected commit dates and topological levels cannot be compared directly.
While writing on top of a split commit-graph, we check if the tip of the chain has a GDAT chunk. If it does, we append to the chain, writing GDAT chunk. Thus, we guarantee if the topmost split commit-graph file has a GDAT chunk, rest of the chain does too.
Quoted 4 lines
If the topmost split commit-graph file does not have a GDAT chunk (meaning it has been appended by the old Git), we write without GDAT chunk. We do write a GDAT chunk when the existing chain does not have GDAT chunk - when we are writing to the commit-graph chain with the 'replace' strategy.
I think the last paragraph can be simplified (or added to) by explicitly stating the goal:
When adding new layer to the split commit-graph file, and when merging some or all layers (replacing them in the latter case), the new layer will have GDAT chunk if and only if in the final result there would be no layer without GDAT chunk just below it.
Quoted 2 lines
Thanks to Dr. Stolee, Dr. Narębski, and Taylor for their reviews.
You are welcome.
Quoted 47 lines
I look forward to everyone's reviews!
Thanks
* Abhishek
----------------------------------------------------------------------------
Changes in version 4:
* Added GDOV to handle overflows in generation data.
* Added a test for writing tip graph for a generation number v2 graph chain int5324-split-commit-graph.sh
* Added a section on how mixed generation number chains are handled inDocumentation/technical/commit-graph-format.txt
* Reverted unimportant whitespace style changes incommit-graph.c
* Added header comments about the order of comparision forcompare_commits_by_gen_then_commit_dateincommit.h,compare_commits_by_genincommit-graph.h
* Elaborated on why t6404 fails with corrected commit date and must be run with GIT_TEST_COMMIT_GRAPH=1 in the commit "commit-reach: use corrected commit dates inpaint_down_to_common()"
* Elaborated on write behavior for mixed generation number chains in the commit "commit-graph: use generation v2 only if entire chain does"
* Added notes about adding thetopo_levelslab to structwrite_commit_graph_contextas well as structcommit_graph.
* Clarified commit message for "commit-graph: consolidatefill_commit_graph_info"
* Removed the claim "GDAT can store future generation numbers" because it hasn't been tested yet.Changes in version 3:
* Reordered patches as discussed in 2
[https://lore.kernel.org/git/[email protected]/]
.
* Split "implement corrected commit date" into two patches - one introducing the topo level slab and other implementing corrected commit dates.
* Extended split-commit-graph tests to verify at the end of test.
* Use topological levels as generation number if any of split commit-graph files do not have generation data chunk.Changes in version 2:
Quoted 11 lines
* Add tests for generation data chunk.
* Add an optionGIT_TEST_COMMIT_GRAPH_NO_GDATto control whether to write generation data chunk.
* Compare commits with corrected commit dates if present inpaint_down_to_common().
* Update technical documentation.
* Handle mixed generation commit chains.
* Improve commit messages for "commit-graph: fix regression when computing bloom filter", "commit-graph: consolidatefill_commit_graph_info",
* Revert unnecessary whitespace changes.
* Splituint_32->timestamp_tchange into a new commit.
After careful review of those 10 patches it looks like the series is close to being ready, requiring only small changes to progress.
Quoted 2 lines
Abhishek Kumar (10):
commit-graph: fix regression when computing Bloom filters
All good, beside possible improvement to the commit message. Thanks to Taylor Blau for discovering possible reason for strange no change in performance.
Quoted 1 line
revision: parse parent in
indegree_walk_step()
Looks good.
Quoted 1 line
commit-graph: consolidate
fill_commit_graph_info
Needs to fix now duplicated test names (minor change). Proposed possible improvement to the commit message.
Quoted 1 line
commit-graph: return 64-bit generation number
Needs fixing due to mismerge: there should be no switch from using GENERATION_NUMBER_ZERO to using GENERATION_NUMBER_INFINITY. Possible minor improvement to the commit message.
Quoted 1 line
commit-graph: add a slab to store topological levels
Possible minor improvement to the commit message.
There is also not very important issue, but something that would be nice to explain, namely that checks for GENERATION_NUMBER_INFINITY can never be true, as topo_level_slab_at() returns 0 for commits outside the commit-graph, not GENERATION_NUMBER_INFINITY. It works but it is not obvious why.
Quoted 1 line
commit-graph: implement corrected commit date
The change to commit-graph verification needs fixing, and we need to decide how verifying generation numbers should work. Perhaps a test for handling topological level of GENERATION_NUMBER_V1_MAX could be added (though this might be left for ater).
The changes to git commit-graph verify code could be put into separate patch, either before or after this one.
Quoted 1 line
commit-graph: implement generation data chunk
Proposed possible improvement to the commit message. The commit message does not explain why given shape of history is needed to test handling corrected commit date offset overflow.
Proposed minor corrections to the coding style.
Instead of looping again through all commits when handling overflow in corrected commit date offsets, while there should be at most a few commits needing it, why not save those commits on list and loop only through those commits? Though this _possible_ performance improvement could be left to the followup...
test_commit_with_date() could be instead implemented via adding --date <date> option to test_commit() in test-lib-functions.sh.
Also, to reduce "noise" in this patch, the rename of run_three_modes() to run_all_modes() and test_three_modes() to test_all_modes() could have been done in a separate preparatory patch. It would be pure refactoring patch, without introducing any new functionality. But it is not something that is necessary.
Quoted 1 line
commit-graph: use generation v2 only if entire chain does
Proposed possible improvement to the commit message. Proposed minor corrections to the coding style (also in tests).
There is a question whether merging layers or replacing them should honor GIT_TEST_COMMIT_GRAPH_NO_GDAT.
Tests possibly could be made more strict, and check more things explicitly. One test we are missing is testing that merging layers is done correctly, namely that if we are merging layers in split commit-graph file, and the layer below the ones we are merging lacks GDAT chunk, then the result of the merge should also be without GDAT chunk -- but that might be left for later.
Quoted 1 line
commit-reach: use corrected commit dates in
paint_down_to_common()
This patch consist of two slightly interleaved changes, which possibly could be separated: change to paint_down_to_common() and change to t6404-recursive-merge test.
In the commit message for the paint_down_to_common() we should explicitly mention 091f4cf3, which this one partially reverts.
Possible accidental change, question about function naming.
Quoted 1 line
doc: add corrected commit date info
Needs further improvements to the documentation, like adding "[Optional]" to chunk description, and leftover switching from "generation numbers" to "topological levels" in one place.
Quoted 22 lines
.../technical/commit-graph-format.txt | 21 +- Documentation/technical/commit-graph.txt | 62 ++++- commit-graph.c | 256 ++++++++++++++---- commit-graph.h | 17 +- commit-reach.c | 38 +-- commit-reach.h | 2 +- commit.c | 4 +- commit.h | 5 +- revision.c | 13 +- t/README | 3 + t/helper/test-read-graph.c | 4 + t/t4216-log-bloom.sh | 4 +- t/t5000-tar-tree.sh | 20 +- t/t5318-commit-graph.sh | 70 ++++- t/t5324-split-commit-graph.sh | 98 ++++++- t/t6404-recursive-merge.sh | 5 +- t/t6600-test-reach.sh | 68 ++--- upload-pack.c | 2 +- 18 files changed, 534 insertions(+), 158 deletions(-)
Quoted 4 lines
base-commit:
d98273ba77e1ab9ec755576bc86c716a97bf59d7
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-676%2Fabhishekkumar2718%2Fcorrected_commit_date-v4
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-676/abhishekkumar2718/corrected_commit_date-v4
Pull-Request: https://github.com/gitgitgadget/git/pull/676
[...]
Best,
Signature · 1 line
Jakub Narębski
Quoted 8 lines · Jakub Narębski
Hi Abhishek,
Quoted 2 lines · Abhishek Kumar via GitGitGadget
This patch series implements the corrected commit date offsets as generation number v2, along with other pre-requisites.
Thanks a lot for continued working on this patch series.
Thank you so much for the careful review of the series.
Quoted 9 lines
Quoted 8 lines
Git uses topological levels in the commit-graph file for commit-graph traversal operations like git log --graph. Unfortunately, using topological levels can result in a worse performance than without them when compared with committer date as a heuristics. For example, git merge-base v4.8 v4.9 on the Linux repository walks 635,579 commits using topological levels and walks 167,468 using committer date.
Quoted 3 lines
Very minor nitpick: it would make it easier to read if the commands themself would be put inside single quotes or backticks, e.g.
git log --graphandgit merge-base v4.8 v4.9.
That's unexpected - I wrote the commands within single quotes in the pull request. Since backticks are rendered as "code-tags" on Github, let me try single quotes.
Quoted 6 lines
I wonder if it is worth mentioning (probably not) that this performance hit was the reason why since
091f4cf3git merge-baseuses committer date heuristics unless there is a cutoff and using topological levels (generation date v1) is expected to give better performance.
I think that's useful context for someone wondering whether we continue to take the performance hit with topological levels or have abandoned topological levels or chosen some another alternative altogether.
Quoted 7 lines
Quoted 4 lines
Thus, the need for generation number v2 was born. New generation number needed to provide good performance, increment updates, and backward compatibility. Due to an unfortunate problem 1
Minor issue: this looks a bit strange; is there an error in formatting this part?
Yes. The plaintext in pull request description reads as follows:
Thus, the need for generation number v2 was born. New generation number needed to provide good performance, increment updates, and backward compatibility. Due to an unfortunate problem [1], we also needed a way to distinguish between the old and new generation number without incrementing graph version.
[1]: https://public-inbox.org/git/[email protected]/
I have been reviewing other pull request descriptions to match their style (and hope the cover letter renders correctly) and Dr. Stolee in his patch series to "add --literal value" option to configuration has written:
As reported [1], 'git maintenance unregister' fails when a repository is located in a directory with regex glob characters.
[1] https://lore.kernel.org/git/[email protected]/T/#mb96fa4187a0d6aeda097cd95804a8aafc0273022
(Note the lack of colon after [1])
Quoted 63 lines
Quoted 11 lines
[https://public-inbox.org/git/[email protected]/], we also needed a way to distinguish between the old and new generation number without incrementing graph version.
Various candidates were examined (https://github.com/derrickstolee/gen-test,
https://github.com/abhishekkumar2718/git/pull/1). The proposed generation number v2, Corrected Commit Date with Mononotically Increasing Offsets performed much worse than committer date (506,577 vs. 167,468 commits walked for git merge-base v4.8 v4.9) and was dropped.Quoted 3 lines
Using Generation Data chunk (GDAT) relieves the requirement of backward compatibility as we would continue to store topological levels in Commit Data (CDAT) chunk.
Nice writeup about the history of generation number v2, much appreciated.
Quoted 2 lines
Thus, Corrected Commit Date was chosen as generation
number v2. The Corrected Commit Date is defined as:Minor nitpick: it would be probably better to use "is defined as follows." instead of "is defined as:".
Quoted 7 lines
For a commit C, let its corrected commit date be the maximum of the commit date of C and the corrected commit dates of its parents plus 1. Then corrected commit date offset is the difference between corrected commit date of C and commit date of C. As a special case, a root commit with timestamp zero has corrected commit date of 1 to be able distinguish it from
GENERATION_NUMBER_ZERO(that is, an uncomputed corrected commit date).Very minor nitpick: s/with timestamp/with *the* timestamp/, and s/to be able distinguish/to be able *to* distinguish/ (without the '*' used to mark the additions).
Quoted 2 lines
We will introduce an additional commit-graph chunk, Generation Data chunk,
Or "Generation DATa chunk", if we want to emphasize where its name came from, or even "Generation DATa (GDAT) chunk". But it is fine as it is now, though it would be good idea to write "Generation Data (GDAT) chunk" to explicitly state its name / shortcut.
Quoted 5 lines
and store corrected commit date offsets in GDAT chunk while storing topological levels in CDAT chunk. The old versions of Git would ignore GDAT chunk, using topological levels from CDAT chunk. In contrast, new versions of Git would use corrected commit dates, falling back to topological level if the generation data chunk is absent in the commit-graph file.
Nice writeup of handling the backward compatibility.
Quoted 8 lines
While storing corrected commit date offsets saves us 4 bytes per commit (as compared with storing corrected commit dates directly), it's possible for the offset to overflow the space allocated. To handle such cases, we introduce a new chunk, Generation Data Overflow (GDOV) that stores the corrected commit date. For overflowing offsets, we set MSB and store the position into the GDOV chunk, in a mechanism similar to the Extra Edges list chunk.
Very minor suggestion: perhaps it would be better to use "it's however possible".
Quoted 8 lines
Very minor suggestion: "it's possible for the offset to overflow" could be simplified to just "the offset can overflow"... though the simplified version loses a bit of hint that the overflow should be very rare in real repositories.
But it is just fine as it is now; I am not a native English speaker to judge which version is better.
I think it is better to indicate the rareness of overflows.
Quoted 21 lines
Quoted 16 lines
For mixed generation number environment (for example new Git on the command line, old Git used by GUI client), we can encounter a mixed-chain commit-graph (a commit-graph chain where some of split commit-graph files have GDAT chunk and others do not). As backward compatibility is one of the goals, we can define the following behavior:
While reading a mixed-chain commit-graph version, we fall back on topological levels as corrected commit dates and topological levels cannot be compared directly.
While writing on top of a split commit-graph, we check if the tip of the chain has a GDAT chunk. If it does, we append to the chain, writing GDAT chunk. Thus, we guarantee if the topmost split commit-graph file has a GDAT chunk, rest of the chain does too.
Quoted 4 lines
If the topmost split commit-graph file does not have a GDAT chunk (meaning it has been appended by the old Git), we write without GDAT chunk. We do write a GDAT chunk when the existing chain does not have GDAT chunk - when we are writing to the commit-graph chain with the 'replace' strategy.
Quoted 8 lines
I think the last paragraph can be simplified (or added to) by explicitly stating the goal:
When adding new layer to the split commit-graph file, and when merging some or all layers (replacing them in the latter case), the new layer will have GDAT chunk if and only if in the final result there would be no layer without GDAT chunk just below it.
Thanks, that is much clearer to understand.
Quoted 5 lines
...
After careful review of those 10 patches it looks like the series is close to being ready, requiring only small changes to progress.
Thank you for writing this handy reference for changes.
Quoted 48 lines
Quoted 2 lines
Abhishek Kumar (10):
commit-graph: fix regression when computing Bloom filtersAll good, beside possible improvement to the commit message. Thanks to Taylor Blau for discovering possible reason for strange no change in performance.
Quoted 1 line
revision: parse parent in
indegree_walk_step()Looks good.
Quoted 1 line
commit-graph: consolidate
fill_commit_graph_infoNeeds to fix now duplicated test names (minor change). Proposed possible improvement to the commit message.
Quoted 1 line
commit-graph: return 64-bit generation number
Needs fixing due to mismerge: there should be no switch from using
GENERATION_NUMBER_ZEROto usingGENERATION_NUMBER_INFINITY. Possible minor improvement to the commit message.Quoted 1 line
commit-graph: add a slab to store topological levels
Possible minor improvement to the commit message.
There is also not very important issue, but something that would be nice to explain, namely that checks for
GENERATION_NUMBER_INFINITYcan never be true, astopo_level_slab_at()returns 0 for commits outside the commit-graph, notGENERATION_NUMBER_INFINITY. It works but it is not obvious why.Quoted 1 line
commit-graph: implement corrected commit date
The change to commit-graph verification needs fixing, and we need to decide how verifying generation numbers should work. Perhaps a test for handling topological level of
GENERATION_NUMBER_V1_MAXcould be added (though this might be left for ater).The changes to
git commit-graph verifycode could be put into separate patch, either before or after this one.Quoted 1 line
commit-graph: implement generation data chunk
Proposed possible improvement to the commit message. The commit message does not explain why given shape of history is needed to test handling corrected commit date offset overflow.
Quoted 7 lines
Proposed minor corrections to the coding style.
Instead of looping again through all commits when handling overflow in corrected commit date offsets, while there should be at most a few commits needing it, why not save those commits on list and loop only through those commits? Though this _possible_ performance improvement could be left to the followup...
Since the improvement can be applied to both write_graph_chunk_generation_data_overflow() and write_graph_chunk_extra_edges(), I am planning to cover this in a followup.
Quoted 38 lines
test_commit_with_date()could be instead implemented via adding--date <date>option totest_commit()intest-lib-functions.sh.Also, to reduce "noise" in this patch, the rename of
run_three_modes()torun_all_modes()andtest_three_modes()totest_all_modes()could have been done in a separate preparatory patch. It would be pure refactoring patch, without introducing any new functionality. But it is not something that is necessary.Quoted 1 line
commit-graph: use generation v2 only if entire chain does
Proposed possible improvement to the commit message. Proposed minor corrections to the coding style (also in tests).
There is a question whether merging layers or replacing them should honor
GIT_TEST_COMMIT_GRAPH_NO_GDAT.Tests possibly could be made more strict, and check more things explicitly. One test we are missing is testing that merging layers is done correctly, namely that if we are merging layers in split commit-graph file, and the layer below the ones we are merging lacks GDAT chunk, then the result of the merge should also be without GDAT chunk -- but that might be left for later.
Quoted 1 line
commit-reach: use corrected commit dates in
paint_down_to_common()This patch consist of two slightly interleaved changes, which possibly could be separated: change to
paint_down_to_common()and change to t6404-recursive-merge test.In the commit message for the
paint_down_to_common()we should explicitly mention091f4cf3, which this one partially reverts.Possible accidental change, question about function naming.
Quoted 1 line
doc: add corrected commit date info
Quoted 9 lines
Needs further improvements to the documentation, like adding "[Optional]" to chunk description, and leftover switching from "generation numbers" to "topological levels" in one place.
...
Best,
Signature · 1 line
Jakub Narębski
Thanks
- Abhishek