History

Fix crash when staging a hunk that overlaps multiple unstaged hunks (#27545)

Release Notes: - Git: Fix crash when staging a hunk that overlaps multiple unstaged hunks. ---------

João Marcos committed 1 year ago

Commit e6357981 parent 69247201 file · +123 −41Browse files

Showing the changes to crates/buffer_diff/src/buffer_diff.rs (1 of 1 file). Show all

Unified · Split

crates/buffer_diff/src/buffer_diff.rs+123−41
@@ -3,9 +3,7 @@ use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as · 2 unmodified lines
33 use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task};
44 use language::{Language, LanguageRegistry};
55 use rope::Rope;
6−use std::cmp::Ordering;
7−use std::mem;
8−use std::{future::Future, iter, ops::Range, sync::Arc};
6+use std::{cmp::Ordering, future::Future, iter, mem, ops::Range, sync::Arc};
97 use sum_tree::SumTree;
108 use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point, ToOffset as _};
119 use util::ResultExt;
@@ -189,7 +187,7 @@ impl BufferDiffSnapshot { · 177 unmodified lines
189187 impl BufferDiffInner {
190188 /// Returns the new index text and new pending hunks.
191189 fn stage_or_unstage_hunks_impl(
192− &mut self,
190+ &self,
193191 unstaged_diff: &Self,
194192 stage: bool,
195193 hunks: &[DiffHunk],
@@ -261,7 +259,6 @@ impl BufferDiffInner { · 65 unmodified lines
261259 old_pending_hunks.next(buffer);
262260 }
263261
264− // merge into pending hunks
265262 if (stage && secondary_status == DiffHunkSecondaryStatus::NoSecondaryHunk)
266263 || (!stage && secondary_status == DiffHunkSecondaryStatus::HasSecondaryHunk)
267264 {
@@ -288,56 +285,71 @@ impl BufferDiffInner { · 20 unmodified lines
288285 let mut unstaged_hunk_cursor = unstaged_diff.hunks.cursor::<DiffHunkSummary>(buffer);
289286 unstaged_hunk_cursor.next(buffer);
290287
291− let mut prev_unstaged_hunk_buffer_offset = 0;
292− let mut prev_unstaged_hunk_base_text_offset = 0;
293− let mut edits = Vec::<(Range<usize>, String)>::new();
294−
295288 // then, iterate over all pending hunks (both new ones and the existing ones) and compute the edits
296− for PendingHunk {
289+ let mut prev_unstaged_hunk_buffer_end = 0;
290+ let mut prev_unstaged_hunk_base_text_end = 0;
291+ let mut edits = Vec::<(Range<usize>, String)>::new();
292+ let mut pending_hunks_iter = pending_hunks.iter().cloned().peekable();
293+ while let Some(PendingHunk {
297294 buffer_range,
298295 diff_base_byte_range,
299296 ..
300− } in pending_hunks.iter().cloned()
297+ }) = pending_hunks_iter.next()
301298 {
302− let skipped_hunks = unstaged_hunk_cursor.slice(&buffer_range.start, Bias::Left, buffer);
299+ // Advance unstaged_hunk_cursor to skip unstaged hunks before current hunk
300+ let skipped_unstaged =
301+ unstaged_hunk_cursor.slice(&buffer_range.start, Bias::Left, buffer);
303302
304− if let Some(secondary_hunk) = skipped_hunks.last() {
305− prev_unstaged_hunk_base_text_offset = secondary_hunk.diff_base_byte_range.end;
306− prev_unstaged_hunk_buffer_offset =
307− secondary_hunk.buffer_range.end.to_offset(buffer);
303+ if let Some(unstaged_hunk) = skipped_unstaged.last() {
304+ prev_unstaged_hunk_base_text_end = unstaged_hunk.diff_base_byte_range.end;
305+ prev_unstaged_hunk_buffer_end = unstaged_hunk.buffer_range.end.to_offset(buffer);
308306 }
309307
308+ // Find where this hunk is in the index if it doesn't overlap
310309 let mut buffer_offset_range = buffer_range.to_offset(buffer);
311− let start_overshoot = buffer_offset_range.start - prev_unstaged_hunk_buffer_offset;
312− let mut index_start = prev_unstaged_hunk_base_text_offset + start_overshoot;
313−
314− while let Some(unstaged_hunk) = unstaged_hunk_cursor.item().filter(|item| {
315− item.buffer_range
316− .start
317− .cmp(&buffer_range.end, buffer)
318− .is_le()
319− }) {
320− let unstaged_hunk_offset_range = unstaged_hunk.buffer_range.to_offset(buffer);
321− prev_unstaged_hunk_base_text_offset = unstaged_hunk.diff_base_byte_range.end;
322− prev_unstaged_hunk_buffer_offset = unstaged_hunk_offset_range.end;
310+ let start_overshoot = buffer_offset_range.start - prev_unstaged_hunk_buffer_end;
311+ let mut index_start = prev_unstaged_hunk_base_text_end + start_overshoot;
312+
313+ loop {
314+ // Merge this hunk with any overlapping unstaged hunks.
315+ if let Some(unstaged_hunk) = unstaged_hunk_cursor.item() {
316+ let unstaged_hunk_offset_range = unstaged_hunk.buffer_range.to_offset(buffer);
317+ if unstaged_hunk_offset_range.start <= buffer_offset_range.end {
318+ prev_unstaged_hunk_base_text_end = unstaged_hunk.diff_base_byte_range.end;
319+ prev_unstaged_hunk_buffer_end = unstaged_hunk_offset_range.end;
320+
321+ index_start = index_start.min(unstaged_hunk.diff_base_byte_range.start);
322+ buffer_offset_range.start = buffer_offset_range
323+ .start
324+ .min(unstaged_hunk_offset_range.start);
325+ buffer_offset_range.end =
326+ buffer_offset_range.end.max(unstaged_hunk_offset_range.end);
327+
328+ unstaged_hunk_cursor.next(buffer);
329+ continue;
330+ }
331+ }
323332
324− index_start = index_start.min(unstaged_hunk.diff_base_byte_range.start);
325− buffer_offset_range.start = buffer_offset_range
326− .start
327− .min(unstaged_hunk_offset_range.start);
333+ // If any unstaged hunks were merged, then subsequent pending hunks may
334+ // now overlap this hunk. Merge them.
335+ if let Some(next_pending_hunk) = pending_hunks_iter.peek() {
336+ let next_pending_hunk_offset_range =
337+ next_pending_hunk.buffer_range.to_offset(buffer);
338+ if next_pending_hunk_offset_range.start <= buffer_offset_range.end {
339+ buffer_offset_range.end = next_pending_hunk_offset_range.end;
340+ pending_hunks_iter.next();
341+ continue;
342+ }
343+ }
328344
329− unstaged_hunk_cursor.next(buffer);
345+ break;
330346 }
331347
332348 let end_overshoot = buffer_offset_range
333349 .end
334− .saturating_sub(prev_unstaged_hunk_buffer_offset);
335− let index_end = prev_unstaged_hunk_base_text_offset + end_overshoot;
336−
337− let index_range = index_start..index_end;
338− buffer_offset_range.end = buffer_offset_range
339− .end
340− .max(prev_unstaged_hunk_buffer_offset);
350+ .saturating_sub(prev_unstaged_hunk_buffer_end);
351+ let index_end = prev_unstaged_hunk_base_text_end + end_overshoot;
352+ let index_byte_range = index_start..index_end;
341353
342354 let replacement_text = if stage {
343355 log::debug!("stage hunk {:?}", buffer_offset_range);
@@ -351,8 +363,9 @@ impl BufferDiffInner { · 7 unmodified lines
351363 .collect::<String>()
352364 };
353365
354− edits.push((index_range, replacement_text));
366+ edits.push((index_byte_range, replacement_text));
355367 }
368+ drop(pending_hunks_iter);
356369
357370 #[cfg(debug_assertions)] // invariants: non-overlapping and sorted
358371 {
@@ -1649,6 +1662,75 @@ mod tests { · 1290 unmodified lines
16491662 "
16501663 .unindent(),
16511664 },
1665+ Example {
1666+ name: "one unstaged hunk that contains two uncommitted hunks",
1667+ head_text: "
1668+ one
1669+ two
1670+
1671+ three
1672+ four
1673+ "
1674+ .unindent(),
1675+ index_text: "
1676+ one
1677+ two
1678+ three
1679+ four
1680+ "
1681+ .unindent(),
1682+ buffer_marked_text: "
1683+ «one
1684+
1685+ three // modified
1686+ four»
1687+ "
1688+ .unindent(),
1689+ final_index_text: "
1690+ one
1691+
1692+ three // modified
1693+ four
1694+ "
1695+ .unindent(),
1696+ },
1697+ Example {
1698+ name: "one uncommitted hunk that contains two unstaged hunks",
1699+ head_text: "
1700+ one
1701+ two
1702+ three
1703+ four
1704+ five
1705+ "
1706+ .unindent(),
1707+ index_text: "
1708+ ZERO
1709+ one
1710+ TWO
1711+ THREE
1712+ FOUR
1713+ five
1714+ "
1715+ .unindent(),
1716+ buffer_marked_text: "
1717+ «one
1718+ TWO_HUNDRED
1719+ THREE
1720+ FOUR_HUNDRED
1721+ five»
1722+ "
1723+ .unindent(),
1724+ final_index_text: "
1725+ ZERO
1726+ one
1727+ TWO_HUNDRED
1728+ THREE
1729+ FOUR_HUNDRED
1730+ five
1731+ "
1732+ .unindent(),
1733+ },
16521734 ];
16531735
16541736 for example in table {

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t