From 8c8639ffcce20547f6fee194b814caa5af20b9c6 Mon Sep 17 00:00:00 2001 From: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> Date: Wed, 21 Jan 2026 01:37:33 -0500 Subject: [PATCH] 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 --- crates/buffer_diff/src/buffer_diff.rs | 70 ++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/crates/buffer_diff/src/buffer_diff.rs b/crates/buffer_diff/src/buffer_diff.rs index 385edad20ff0e385fe76c107576ee311b8d81357..c10f2075043615c1af589cfa031f15bb04f2e892 100644 --- a/crates/buffer_diff/src/buffer_diff.rs +++ b/crates/buffer_diff/src/buffer_diff.rs @@ -536,7 +536,9 @@ impl BufferDiffInner> { 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 = "