Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions odb/source-packed.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,35 @@ static int find_pack_entry(struct odb_source_packed *store,
}
}

/*
* Recovery for a concurrent-repack race: a MIDX can name an owning
* pack for an object that a simultaneous repack has since deleted,
* even though the object still exists in another pack the same MIDX
* covers (e.g. a kept base pack that geometric repack did not rewrite).
* If the object is present in a MIDX yet none of the paths above could
* serve it, its recorded owning pack has become unavailable. The
* regular fallback above deliberately skips MIDX-covered packs, so
* scan this MIDX's packs directly to find the surviving copy. The
* bsearch gate keeps genuine misses (objects absent from the MIDX) on
* the fast path.
*/
if (store->midx) {
struct multi_pack_index *m = store->midx;
uint32_t midx_pos, i;

if (bsearch_midx(oid, m, &midx_pos)) {
for (i = 0; i < m->num_packs + m->num_packs_in_base; i++) {
struct packed_git *p;

if (prepare_midx_pack(m, i))
continue;
p = nth_midxed_pack(m, i);
if (p && packfile_fill_entry(p, oid, e))
return 1;
}
}
}

return 0;
}

Expand Down
7 changes: 7 additions & 0 deletions replay.c
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,13 @@ static struct commit *pick_regular_commit(struct repository *repo,
merge_opt->ancestor = NULL;
merge_opt->branch2 = NULL;

if (result->clean < 0) {
error(_("merge of %s onto %s failed"),
oid_to_hex(&pickme->object.oid),
oid_to_hex(&replayed_base->object.oid));
return NULL;
}

if (!result->clean)
return NULL;

Expand Down
35 changes: 35 additions & 0 deletions t/t3650-replay-basics.sh
Original file line number Diff line number Diff line change
Expand Up @@ -565,4 +565,39 @@ test_expect_success '--onto with --ref rejects multiple revision ranges' '
test_grep "cannot be used with multiple revision ranges" err
'

test_expect_success 'replay fails without segfault when objects are missing' '
test_when_finished "rm -fr unreadable" &&
git init unreadable &&
(
cd unreadable &&

test_write_lines l1 l2 l3 l4 l5 l6 l7 l8 >f &&
git add f &&
git commit -m base &&
git branch base &&

test_write_lines l1 l2 l3 l4 l5 l6 l7 CHANGED >f &&
git commit -am side &&
git branch side &&

git switch -c onto base &&
test_write_lines CHANGED l2 l3 l4 l5 l6 l7 l8 >f &&
git commit -am onto &&

# The replay works while every object is readable.
git replay --onto onto base..side &&

# Removing the onto tree makes parse_tree() fail during the
# incore merge, driving clean < 0 with a NULL result tree.
onto_tree=$(git rev-parse onto^{tree}) &&
obj=$(test_oid_to_path "$onto_tree") &&
mv .git/objects/${obj} saved-tree &&

# Ensure replay gracefully handles the missing object
test_must_fail git replay --onto onto base..side 2>err &&
test_grep ! "[Ss]egmentation" err &&
test_grep "Could not read\|collecting merge info failed" err
)
'

test_done
40 changes: 40 additions & 0 deletions t/t5319-multi-pack-index.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1393,4 +1393,44 @@ test_expect_success 'pack.preferBitmapTips interprets patterns as hierarchy' '
)
'

test_expect_success 'lookup recovers object whose midx-owning pack was removed' '
test_when_finished "rm -fr repo" &&
git init repo &&
(
cd repo &&

# "keep" ends up only in the big pack; "dup" is deliberately
# placed in two packs so the midx has to choose an owner.
test_commit keep &&
echo duplicated-content >dup &&
git add dup &&
git commit -m dup &&
dup_oid=$(git rev-parse HEAD:dup) &&

# Roll every object, including dup, into a single big pack.
git repack -adq &&

# Build a second, "moderate" pack that also contains dup, so dup
# now lives in two packs that the midx will cover.
moderate=$(echo "$dup_oid" |
git pack-objects --quiet $objdir/pack/pack) &&

# Attribute dup to the moderate pack in the midx.
git multi-pack-index write \
--preferred-pack="pack-$moderate.idx" &&

# Simulate a concurrent "git repack" retiring the moderate pack:
# its files disappear, but the now-stale midx still names it as
# the owner of dup. A valid copy of dup survives in the big pack.
rm -f $objdir/pack/pack-$moderate.* &&

# The midx routes the lookup to the deleted pack, and the regular
# pack fallback skips midx-covered packs, so without recovery dup
# would appear missing even though it is physically present.
echo blob >expect &&
git cat-file -t "$dup_oid" >actual &&
test_cmp expect actual
)
'

test_done
Loading