pack-bitmap: add load corrupt bitmap test

t5310 lacks a test to ensure git works correctly when commit bitmap data is corrupted. So this patch add test helper in pack-bitmap.c to list each commit bitmap position in bitmap file and `load corrupt bitmap` test case in t/t5310 to corrupt a commit bitmap before loading it. Signed-off-by: Lidong Yan <502024330056@smail.nju.edu.cn> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Lidong Yan committed Jul 1, 2025 at 05:32 UTC bfd5522e98cead32d7bbdf54eca4ffeb3e01fa6b
4 files changed +96 -5
pack-bitmap.c
+57 -5
@@ -31,6 +31,7 @@ struct stored_bitmap {
31 struct object_id oid;
32 struct ewah_bitmap *root;
33 struct stored_bitmap *xor;
34 + size_t map_pos;
35 int flags;
36 };
37
@@ -314,13 +315,14 @@ static struct stored_bitmap *store_bitmap(struct bitmap_index *index,
315 struct ewah_bitmap *root,
316 const struct object_id *oid,
317 struct stored_bitmap *xor_with,
317 - int flags)
318 + int flags, size_t map_pos)
319 {
320 struct stored_bitmap *stored;
321 khiter_t hash_pos;
322 int ret;
323
324 stored = xmalloc(sizeof(struct stored_bitmap));
325 + stored->map_pos = map_pos;
326 stored->root = root;
327 stored->xor = xor_with;
328 stored->flags = flags;
@@ -376,10 +378,12 @@ static int load_bitmap_entries_v1(struct bitmap_index *index)
378 struct stored_bitmap *xor_bitmap = NULL;
379 uint32_t commit_idx_pos;
380 struct object_id oid;
381 + size_t entry_map_pos;
382
383 if (index->map_size - index->map_pos < 6)
384 return error(_("corrupt ewah bitmap: truncated header for entry %d"), i);
385
386 + entry_map_pos = index->map_pos;
387 commit_idx_pos = read_be32(index->map, &index->map_pos);
388 xor_offset = read_u8(index->map, &index->map_pos);
389 flags = read_u8(index->map, &index->map_pos);
@@ -402,8 +406,9 @@ static int load_bitmap_entries_v1(struct bitmap_index *index)
406 if (!bitmap)
407 return -1;
408
405 - recent_bitmaps[i % MAX_XOR_OFFSET] = store_bitmap(
406 - index, bitmap, &oid, xor_bitmap, flags);
409 + recent_bitmaps[i % MAX_XOR_OFFSET] =
410 + store_bitmap(index, bitmap, &oid, xor_bitmap, flags,
411 + entry_map_pos);
412 }
413
414 return 0;
@@ -869,6 +874,7 @@ static struct stored_bitmap *lazy_bitmap_for_commit(struct bitmap_index *bitmap_
874 int xor_flags;
875 khiter_t hash_pos;
876 struct bitmap_lookup_table_xor_item *xor_item;
877 + size_t entry_map_pos;
878
879 if (is_corrupt)
880 return NULL;
@@ -928,6 +934,7 @@ static struct stored_bitmap *lazy_bitmap_for_commit(struct bitmap_index *bitmap_
934 goto corrupt;
935 }
936
937 + entry_map_pos = bitmap_git->map_pos;
938 bitmap_git->map_pos += sizeof(uint32_t) + sizeof(uint8_t);
939 xor_flags = read_u8(bitmap_git->map, &bitmap_git->map_pos);
940 bitmap = read_bitmap_1(bitmap_git);
@@ -935,7 +942,8 @@ static struct stored_bitmap *lazy_bitmap_for_commit(struct bitmap_index *bitmap_
942 if (!bitmap)
943 goto corrupt;
944
938 - xor_bitmap = store_bitmap(bitmap_git, bitmap, &xor_item->oid, xor_bitmap, xor_flags);
945 + xor_bitmap = store_bitmap(bitmap_git, bitmap, &xor_item->oid,
946 + xor_bitmap, xor_flags, entry_map_pos);
947 xor_items_nr--;
948 }
949
@@ -969,6 +977,7 @@ static struct stored_bitmap *lazy_bitmap_for_commit(struct bitmap_index *bitmap_
977 * Instead, we can skip ahead and immediately read the flags and
978 * ewah bitmap.
979 */
980 + entry_map_pos = bitmap_git->map_pos;
981 bitmap_git->map_pos += sizeof(uint32_t) + sizeof(uint8_t);
982 flags = read_u8(bitmap_git->map, &bitmap_git->map_pos);
983 bitmap = read_bitmap_1(bitmap_git);
@@ -976,7 +985,8 @@ static struct stored_bitmap *lazy_bitmap_for_commit(struct bitmap_index *bitmap_
985 if (!bitmap)
986 goto corrupt;
987
979 - return store_bitmap(bitmap_git, bitmap, oid, xor_bitmap, flags);
988 + return store_bitmap(bitmap_git, bitmap, oid, xor_bitmap, flags,
989 + entry_map_pos);
990
991 corrupt:
992 free(xor_items);
@@ -2857,6 +2867,48 @@ int test_bitmap_commits(struct repository *r)
2867 return 0;
2868 }
2869
2870 +int test_bitmap_commits_with_offset(struct repository *r)
2871 +{
2872 + struct object_id oid;
2873 + struct stored_bitmap *stored;
2874 + struct bitmap_index *bitmap_git;
2875 + size_t commit_idx_pos_map_pos, xor_offset_map_pos, flag_map_pos,
2876 + ewah_bitmap_map_pos;
2877 +
2878 + bitmap_git = prepare_bitmap_git(r);
2879 + if (!bitmap_git)
2880 + die(_("failed to load bitmap indexes"));
2881 +
2882 + /*
2883 + * Since this function needs to know the position of each individual
2884 + * bitmap, bypass the commit lookup table (if one exists) by forcing
2885 + * the bitmap to eagerly load its entries.
2886 + */
2887 + if (bitmap_git->table_lookup) {
2888 + if (load_bitmap_entries_v1(bitmap_git) < 0)
2889 + die(_("failed to load bitmap indexes"));
2890 + }
2891 +
2892 + kh_foreach (bitmap_git->bitmaps, oid, stored, {
2893 + commit_idx_pos_map_pos = stored->map_pos;
2894 + xor_offset_map_pos = stored->map_pos + sizeof(uint32_t);
2895 + flag_map_pos = xor_offset_map_pos + sizeof(uint8_t);
2896 + ewah_bitmap_map_pos = flag_map_pos + sizeof(uint8_t);
2897 +
2898 + printf_ln("%s %"PRIuMAX" %"PRIuMAX" %"PRIuMAX" %"PRIuMAX,
2899 + oid_to_hex(&oid),
2900 + (uintmax_t)commit_idx_pos_map_pos,
2901 + (uintmax_t)xor_offset_map_pos,
2902 + (uintmax_t)flag_map_pos,
2903 + (uintmax_t)ewah_bitmap_map_pos);
2904 + })
2905 + ;
2906 +
2907 + free_bitmap_index(bitmap_git);
2908 +
2909 + return 0;
2910 +}
2911 +
2912 int test_bitmap_hashes(struct repository *r)
2913 {
2914 struct bitmap_index *bitmap_git = prepare_bitmap_git(r);
pack-bitmap.h
+1
@@ -81,6 +81,7 @@ void traverse_bitmap_commit_list(struct bitmap_index *,
81 show_reachable_fn show_reachable);
82 void test_bitmap_walk(struct rev_info *revs);
83 int test_bitmap_commits(struct repository *r);
84 +int test_bitmap_commits_with_offset(struct repository *r);
85 int test_bitmap_hashes(struct repository *r);
86 int test_bitmap_pseudo_merges(struct repository *r);
87 int test_bitmap_pseudo_merge_commits(struct repository *r, uint32_t n);
t/helper/test-bitmap.c
+8
@@ -10,6 +10,11 @@ static int bitmap_list_commits(void)
10 return test_bitmap_commits(the_repository);
11 }
12
13 +static int bitmap_list_commits_with_offset(void)
14 +{
15 + return test_bitmap_commits_with_offset(the_repository);
16 +}
17 +
18 static int bitmap_dump_hashes(void)
19 {
20 return test_bitmap_hashes(the_repository);
@@ -36,6 +41,8 @@ int cmd__bitmap(int argc, const char **argv)
41
42 if (argc == 2 && !strcmp(argv[1], "list-commits"))
43 return bitmap_list_commits();
44 + if (argc == 2 && !strcmp(argv[1], "list-commits-with-offset"))
45 + return bitmap_list_commits_with_offset();
46 if (argc == 2 && !strcmp(argv[1], "dump-hashes"))
47 return bitmap_dump_hashes();
48 if (argc == 2 && !strcmp(argv[1], "dump-pseudo-merges"))
@@ -46,6 +53,7 @@ int cmd__bitmap(int argc, const char **argv)
53 return bitmap_dump_pseudo_merge_objects(atoi(argv[2]));
54
55 usage("\ttest-tool bitmap list-commits\n"
56 + "\ttest-tool bitmap list-commits-with-offset\n"
57 "\ttest-tool bitmap dump-hashes\n"
58 "\ttest-tool bitmap dump-pseudo-merges\n"
59 "\ttest-tool bitmap dump-pseudo-merge-commits <n>\n"
t/t5310-pack-bitmaps.sh
+30
@@ -486,6 +486,36 @@ test_bitmap_cases () {
486 grep "ignoring extra bitmap" trace2.txt
487 )
488 '
489 +
490 + test_expect_success 'load corrupt bitmap' '
491 + rm -fr repo &&
492 + git init repo &&
493 + test_when_finished "rm -fr repo" &&
494 + (
495 + cd repo &&
496 + git config pack.writeBitmapLookupTable '"$writeLookupTable"' &&
497 +
498 + test_commit base &&
499 +
500 + git repack -adb &&
501 + bitmap="$(ls .git/objects/pack/pack-*.bitmap)" &&
502 + chmod +w $bitmap &&
503 +
504 + test-tool bitmap list-commits-with-offset >offsets &&
505 + xor_off=$(head -n1 offsets | awk "{print \$3}") &&
506 + printf '\161' |
507 + dd of=$bitmap count=1 bs=1 conv=notrunc seek=$xor_off &&
508 +
509 + git rev-list --objects --no-object-names HEAD >expect.raw &&
510 + git rev-list --objects --use-bitmap-index --no-object-names HEAD \
511 + >actual.raw &&
512 +
513 + sort expect.raw >expect &&
514 + sort actual.raw >actual &&
515 +
516 + test_cmp expect actual
517 + )
518 + '
519 }
520
521 test_bitmap_cases