git: Fix buffer diff crash that could occur during the stage all operation (#47265)

Closes https://github.com/zed-industries/zed/issues/46519

This crash happened because we were incorrectly merging pending and
unstaged hunks together by always using the pending hunk end offset as
the merged hunk bound instead of the max of both hunks.

The fix is to use `max()` when updating `buffer_offset_range.end` during
pending hunk merging, matching the behavior used for unstaged hunk
merging.

Release Notes:

- N/A
This commit is contained in:
Anthony Eid authored and GitHub committed 2026-01-21 06:37:33 +00:00
1 parent df209b6230
commit baf53f8dd9
1 file changed
+69 -1
+69 -1
View File
@@ -536,7 +536,9 @@ impl BufferDiffInner<Entity<language::Buffer>> {
let next_pending_hunk_offset_range =
next_pending_hunk.buffer_range.to_offset(buffer);
if next_pending_hunk_offset_range.start <= buffer_offset_range.end {
buffer_offset_range.end = next_pending_hunk_offset_range.end;
buffer_offset_range.end = buffer_offset_range
.end
.max(next_pending_hunk_offset_range.end);
pending_hunks_iter.next();
continue;
}
@@ -2140,6 +2142,72 @@ mod tests {
}
}
#[gpui::test]
async fn test_stage_all_with_nested_hunks(cx: &mut TestAppContext) {
// This test reproduces a crash where staging all hunks would cause an underflow
// when there's one large unstaged hunk containing multiple uncommitted hunks.
let head_text = "
aaa
bbb
ccc
ddd
eee
fff
ggg
hhh
iii
jjj
kkk
lll
"
.unindent();
let index_text = "
aaa
bbb
CCC-index
DDD-index
EEE-index
FFF-index
GGG-index
HHH-index
III-index
JJJ-index
kkk
lll
"
.unindent();
let buffer_text = "
aaa
bbb
ccc-modified
ddd
eee-modified
fff
ggg
hhh-modified
iii
jjj
kkk
lll
"
.unindent();
let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
let unstaged_diff = cx.new(|cx| BufferDiff::new_with_base_text(&index_text, &buffer, cx));
let uncommitted_diff = cx.new(|cx| {
let mut diff = BufferDiff::new_with_base_text(&head_text, &buffer, cx);
diff.set_secondary_diff(unstaged_diff);
diff
});
uncommitted_diff.update(cx, |diff, cx| {
diff.stage_or_unstage_all_hunks(true, &buffer, true, cx);
});
}
#[gpui::test]
async fn test_toggling_stage_and_unstage_same_hunk(cx: &mut TestAppContext) {
let head_text = "