Skip to content

Commit dbc2747

Browse files
jherlandgitster
authored andcommitted
notes.c: fix off-by-one error when decreasing notes fanout
As noted in the previous commit, the nature of the fanout heuristic in the notes code causes the exact point at which we increase or decrease the notes fanout to vary with the objects being annotated. Since the object ids generated by the test environment are deterministic (by design), the notes generated and tested by t3305 are always the same, and we therefore happen to see the same fanout behavior from one run to the next. Coincidentally, if we were to change the test environment slightly (say by making a test commit on an unrelated branch before we start the t3305 test proper), we not only see the fanout switch happen at different points, we also manage to trigger a _bug_ in the notes code where the fanout 1 -> 0 switch is not applied uniformly across the notes tree, but instead yields a notes tree like this: ... bdeafb301e44b0e4db0f738a2d2a7beefdb70b70 bff2d39b4f7122bd4c5caee3de353a774d1e632a d3/8ec8f851adf470131178085bfbaab4b12ad2a7 e0b173960431a3e692ae929736df3c9b73a11d5b eb3c3aede523d729990ac25c62a93eb47c21e2e3 ... The bug occurs when we are writing out a notes tree with a newly decreased fanout, and the notes tree contains unexpanded subtrees that should be consolidated into the parent tree as a consequence of the decreased fanout): Subtrees that happen to sit at an _even_ level in the internal notes 16-tree structure (in other words: subtrees whose path - "d3" in the example above - is unique in the first nibble - i.e. there are no other note paths that start with "d") are _not_ unpacked as part of the tree writeout. This error will repeat itself in subsequent note trees until the subtree is forced to be unpacked. In t3305 this only happens when the d38ec8f8 note is itself removed from the tree. The error is not severe (no information is lost, and the notes code is able to read/decode this tree and manipulate it correctly), but this is nonetheless a bug in the current implementation that should be fixed. That said, fixing the off-by-one error is not without complications: We must take into account that the load_subtree() call from for_each_note_helper() (that is now done to correctly unpack the subtree while we're writing out the notes tree) may end up inserting unpacked non-notes into the linked list of non_note entries held by the struct notes_tree. Since we are in the process of writing out the notes tree, this linked list is currently in the process of being traversed by write_each_non_note_until(). The unpacked non-notes are necessarily inserted between the last non-note we wrote out, and the next non-note to be written. Hence, we cannot simply hold the next_non_note to write in struct write_each_note_data (as we would then silently skip these newly inserted notes), but must instead always follow the ->next pointer from the last non-note we wrote. (This part was caught by an existing test in t3304.) Cc: Johannes Schindelin <[email protected]> Cc: Brian M. Carlson <[email protected]> Signed-off-by: Johan Herland <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent e1c5253 commit dbc2747

File tree

2 files changed

+18
-8
lines changed

2 files changed

+18
-8
lines changed

notes.c

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -576,16 +576,16 @@ static int for_each_note_helper(struct notes_tree *t, struct int_node *tree,
576576
* the note tree that have not yet been explored. There
577577
* is a direct relationship between subtree entries at
578578
* level 'n' in the tree, and the 'fanout' variable:
579-
* Subtree entries at level 'n <= 2 * fanout' should be
579+
* Subtree entries at level 'n < 2 * fanout' should be
580580
* preserved, since they correspond exactly to a fanout
581581
* directory in the on-disk structure. However, subtree
582-
* entries at level 'n > 2 * fanout' should NOT be
582+
* entries at level 'n >= 2 * fanout' should NOT be
583583
* preserved, but rather consolidated into the above
584584
* notes tree level. We achieve this by unconditionally
585585
* unpacking subtree entries that exist below the
586586
* threshold level at 'n = 2 * fanout'.
587587
*/
588-
if (n <= 2 * fanout &&
588+
if (n < 2 * fanout &&
589589
flags & FOR_EACH_NOTE_YIELD_SUBTREES) {
590590
/* invoke callback with subtree */
591591
unsigned int path_len =
@@ -602,7 +602,7 @@ static int for_each_note_helper(struct notes_tree *t, struct int_node *tree,
602602
path,
603603
cb_data);
604604
}
605-
if (n > fanout * 2 ||
605+
if (n >= 2 * fanout ||
606606
!(flags & FOR_EACH_NOTE_DONT_UNPACK_SUBTREES)) {
607607
/* unpack subtree and resume traversal */
608608
tree->a[i] = NULL;
@@ -723,13 +723,15 @@ static int write_each_note_helper(struct tree_write_stack *tws,
723723

724724
struct write_each_note_data {
725725
struct tree_write_stack *root;
726-
struct non_note *next_non_note;
726+
struct non_note **nn_list;
727+
struct non_note *nn_prev;
727728
};
728729

729730
static int write_each_non_note_until(const char *note_path,
730731
struct write_each_note_data *d)
731732
{
732-
struct non_note *n = d->next_non_note;
733+
struct non_note *p = d->nn_prev;
734+
struct non_note *n = p ? p->next : *d->nn_list;
733735
int cmp = 0, ret;
734736
while (n && (!note_path || (cmp = strcmp(n->path, note_path)) <= 0)) {
735737
if (note_path && cmp == 0)
@@ -740,9 +742,10 @@ static int write_each_non_note_until(const char *note_path,
740742
if (ret)
741743
return ret;
742744
}
745+
p = n;
743746
n = n->next;
744747
}
745-
d->next_non_note = n;
748+
d->nn_prev = p;
746749
return 0;
747750
}
748751

@@ -1144,7 +1147,8 @@ int write_notes_tree(struct notes_tree *t, struct object_id *result)
11441147
strbuf_init(&root.buf, 256 * (32 + the_hash_algo->hexsz)); /* assume 256 entries */
11451148
root.path[0] = root.path[1] = '\0';
11461149
cb_data.root = &root;
1147-
cb_data.next_non_note = t->first_non_note;
1150+
cb_data.nn_list = &(t->first_non_note);
1151+
cb_data.nn_prev = NULL;
11481152

11491153
/* Write tree objects representing current notes tree */
11501154
flags = FOR_EACH_NOTE_DONT_UNPACK_SUBTREES |

t/t3305-notes-fanout.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ all_notes_have_fanout() {
3030
done
3131
}
3232

33+
test_expect_success 'tweak test environment' '
34+
git checkout -b nondeterminism &&
35+
test_commit A &&
36+
git checkout --orphan with_notes;
37+
'
38+
3339
test_expect_success 'creating many notes with git-notes' '
3440
num_notes=300 &&
3541
i=0 &&

0 commit comments

Comments
 (0)