From c9b26e1a8bd9997c71c0e922fa926e6e86b32589 Mon Sep 17 00:00:00 2001 From: "zed-zippy[bot]" <234243425+zed-zippy[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 10:21:21 +0000 Subject: [PATCH] git: Fix buffer diff crash that could occur during the stage all operation (#47265) (cherry-pick to preview) (#47277) Cherry-pick of #47265 to preview ---- 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 Co-authored-by: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> --- 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 e80cf250a4272c58732ab7c4db9d7d472212a6c2..0effc10b9ae8e383ac5f3958f4513568f858e822 100644 --- a/crates/buffer_diff/src/buffer_diff.rs +++ b/crates/buffer_diff/src/buffer_diff.rs @@ -535,7 +535,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; } @@ -2138,6 +2140,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 = "