Skip to content

Commit 1838685

Browse files
jherlandgitster
authored andcommitted
fast-import: Fix incorrect fanout level when modifying existing notes refs
This fixes the bug uncovered by the tests added in the previous two patches. When an existing notes ref was loaded into the fast-import machinery, the num_notes counter associated with that ref remained == 0, even though the true number of notes in the loaded ref was higher. This caused a fanout level of 0 to be used, although the actual fanout of the tree could be > 0. Manipulating the notes tree at an incorrect fanout level causes removals to silently fail, and modifications of existing notes to instead produce an additional note (leaving the old object in place at a different fanout level). This patch fixes the bug by explicitly counting the number of notes in the notes tree whenever it looks like the num_notes counter could be wrong (when num_notes == 0). There may be false positives (i.e. triggering the counting when the notes tree is truly empty), but in those cases, the counting should not take long. Signed-off-by: Johan Herland <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 9ff5e21 commit 1838685

File tree

2 files changed

+29
-7
lines changed

2 files changed

+29
-7
lines changed

fast-import.c

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2173,6 +2173,11 @@ static uintmax_t do_change_note_fanout(
21732173

21742174
if (tmp_hex_sha1_len == 40 && !get_sha1_hex(hex_sha1, sha1)) {
21752175
/* This is a note entry */
2176+
if (fanout == 0xff) {
2177+
/* Counting mode, no rename */
2178+
num_notes++;
2179+
continue;
2180+
}
21762181
construct_path_with_fanout(hex_sha1, fanout, realpath);
21772182
if (!strcmp(fullpath, realpath)) {
21782183
/* Note entry is in correct location */
@@ -2379,7 +2384,7 @@ static void file_change_cr(struct branch *b, int rename)
23792384
leaf.tree);
23802385
}
23812386

2382-
static void note_change_n(struct branch *b, unsigned char old_fanout)
2387+
static void note_change_n(struct branch *b, unsigned char *old_fanout)
23832388
{
23842389
const char *p = command_buf.buf + 2;
23852390
static struct strbuf uq = STRBUF_INIT;
@@ -2390,6 +2395,23 @@ static void note_change_n(struct branch *b, unsigned char old_fanout)
23902395
uint16_t inline_data = 0;
23912396
unsigned char new_fanout;
23922397

2398+
/*
2399+
* When loading a branch, we don't traverse its tree to count the real
2400+
* number of notes (too expensive to do this for all non-note refs).
2401+
* This means that recently loaded notes refs might incorrectly have
2402+
* b->num_notes == 0, and consequently, old_fanout might be wrong.
2403+
*
2404+
* Fix this by traversing the tree and counting the number of notes
2405+
* when b->num_notes == 0. If the notes tree is truly empty, the
2406+
* calculation should not take long.
2407+
*/
2408+
if (b->num_notes == 0 && *old_fanout == 0) {
2409+
/* Invoke change_note_fanout() in "counting mode". */
2410+
b->num_notes = change_note_fanout(&b->branch_tree, 0xff);
2411+
*old_fanout = convert_num_notes_to_fanout(b->num_notes);
2412+
}
2413+
2414+
/* Now parse the notemodify command. */
23932415
/* <dataref> or 'inline' */
23942416
if (*p == ':') {
23952417
char *x;
@@ -2450,7 +2472,7 @@ static void note_change_n(struct branch *b, unsigned char old_fanout)
24502472
typename(type), command_buf.buf);
24512473
}
24522474

2453-
construct_path_with_fanout(sha1_to_hex(commit_sha1), old_fanout, path);
2475+
construct_path_with_fanout(sha1_to_hex(commit_sha1), *old_fanout, path);
24542476
if (tree_content_remove(&b->branch_tree, path, NULL))
24552477
b->num_notes--;
24562478

@@ -2637,7 +2659,7 @@ static void parse_new_commit(void)
26372659
else if (!prefixcmp(command_buf.buf, "C "))
26382660
file_change_cr(b, 0);
26392661
else if (!prefixcmp(command_buf.buf, "N "))
2640-
note_change_n(b, prev_fanout);
2662+
note_change_n(b, &prev_fanout);
26412663
else if (!strcmp("deleteall", command_buf.buf))
26422664
file_change_deleteall(b);
26432665
else if (!prefixcmp(command_buf.buf, "ls "))

t/t9301-fast-import-notes.sh

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,7 @@ EXPECT_END
536536
j=$(($j + 1))
537537
done
538538

539-
test_expect_failure 'change a few existing notes' '
539+
test_expect_success 'change a few existing notes' '
540540
541541
git fast-import <input &&
542542
GIT_NOTES_REF=refs/notes/many_notes git log -n3 refs/heads/many_commits |
@@ -545,7 +545,7 @@ test_expect_failure 'change a few existing notes' '
545545
546546
'
547547

548-
test_expect_failure 'verify that changing notes respect existing fanout' '
548+
test_expect_success 'verify that changing notes respect existing fanout' '
549549
550550
# None of the entries in the top-level notes tree should be a full SHA1
551551
git ls-tree --name-only refs/notes/many_notes |
@@ -594,7 +594,7 @@ EXPECT_END
594594
i=$(($i - 1))
595595
done
596596

597-
test_expect_failure 'remove lots of notes' '
597+
test_expect_success 'remove lots of notes' '
598598
599599
git fast-import <input &&
600600
GIT_NOTES_REF=refs/notes/many_notes git log refs/heads/many_commits |
@@ -603,7 +603,7 @@ test_expect_failure 'remove lots of notes' '
603603
604604
'
605605

606-
test_expect_failure 'verify that removing notes trigger fanout consolidation' '
606+
test_expect_success 'verify that removing notes trigger fanout consolidation' '
607607
608608
# All entries in the top-level notes tree should be a full SHA1
609609
git ls-tree --name-only -r refs/notes/many_notes |

0 commit comments

Comments
 (0)