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
3use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task};3use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task};
4use language::{Language, LanguageRegistry};4use language::{Language, LanguageRegistry};
5use rope::Rope;5use rope::Rope;
6use std::cmp::Ordering;6use std::{cmp::Ordering, future::Future, iter, mem, ops::Range, sync::Arc};
7use std::mem;
8use std::{future::Future, iter, ops::Range, sync::Arc};
9use sum_tree::SumTree;7use sum_tree::SumTree;
10use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point, ToOffset as _};8use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point, ToOffset as _};
11use util::ResultExt;9use util::ResultExt;
@@ -189,7 +187,7 @@ impl BufferDiffSnapshot { · 177 unmodified lines
189impl BufferDiffInner {187impl BufferDiffInner {
190 /// Returns the new index text and new pending hunks.188 /// Returns the new index text and new pending hunks.
191 fn stage_or_unstage_hunks_impl(189 fn stage_or_unstage_hunks_impl(
192 &mut self,190 &self,
193 unstaged_diff: &Self,191 unstaged_diff: &Self,
194 stage: bool,192 stage: bool,
195 hunks: &[DiffHunk],193 hunks: &[DiffHunk],
@@ -261,7 +259,6 @@ impl BufferDiffInner { · 65 unmodified lines
261 old_pending_hunks.next(buffer);259 old_pending_hunks.next(buffer);
262 }260 }
263 261
264 // merge into pending hunks
265 if (stage && secondary_status == DiffHunkSecondaryStatus::NoSecondaryHunk)262 if (stage && secondary_status == DiffHunkSecondaryStatus::NoSecondaryHunk)
266 || (!stage && secondary_status == DiffHunkSecondaryStatus::HasSecondaryHunk)263 || (!stage && secondary_status == DiffHunkSecondaryStatus::HasSecondaryHunk)
267 {264 {
@@ -288,56 +285,71 @@ impl BufferDiffInner { · 20 unmodified lines
288 let mut unstaged_hunk_cursor = unstaged_diff.hunks.cursor::<DiffHunkSummary>(buffer);285 let mut unstaged_hunk_cursor = unstaged_diff.hunks.cursor::<DiffHunkSummary>(buffer);
289 unstaged_hunk_cursor.next(buffer);286 unstaged_hunk_cursor.next(buffer);
290 287
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
295 // then, iterate over all pending hunks (both new ones and the existing ones) and compute the edits288 // 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 {
297 buffer_range,294 buffer_range,
298 diff_base_byte_range,295 diff_base_byte_range,
299 ..296 ..
300 } in pending_hunks.iter().cloned()297 }) = pending_hunks_iter.next()
301 {298 {
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);
303 302
304 if let Some(secondary_hunk) = skipped_hunks.last() {303 if let Some(unstaged_hunk) = skipped_unstaged.last() {
305 prev_unstaged_hunk_base_text_offset = secondary_hunk.diff_base_byte_range.end;304 prev_unstaged_hunk_base_text_end = unstaged_hunk.diff_base_byte_range.end;
306 prev_unstaged_hunk_buffer_offset =305 prev_unstaged_hunk_buffer_end = unstaged_hunk.buffer_range.end.to_offset(buffer);
307 secondary_hunk.buffer_range.end.to_offset(buffer);
308 }306 }
309 307
308 // Find where this hunk is in the index if it doesn't overlap
310 let mut buffer_offset_range = buffer_range.to_offset(buffer);309 let mut buffer_offset_range = buffer_range.to_offset(buffer);
311 let start_overshoot = buffer_offset_range.start - prev_unstaged_hunk_buffer_offset;310 let start_overshoot = buffer_offset_range.start - prev_unstaged_hunk_buffer_end;
312 let mut index_start = prev_unstaged_hunk_base_text_offset + start_overshoot;311 let mut index_start = prev_unstaged_hunk_base_text_end + start_overshoot;
313 312
314 while let Some(unstaged_hunk) = unstaged_hunk_cursor.item().filter(|item| {313 loop {
315 item.buffer_range314 // Merge this hunk with any overlapping unstaged hunks.
316 .start315 if let Some(unstaged_hunk) = unstaged_hunk_cursor.item() {
317 .cmp(&buffer_range.end, buffer)316 let unstaged_hunk_offset_range = unstaged_hunk.buffer_range.to_offset(buffer);
318 .is_le()317 if unstaged_hunk_offset_range.start <= buffer_offset_range.end {
319 }) {318 prev_unstaged_hunk_base_text_end = unstaged_hunk.diff_base_byte_range.end;
320 let unstaged_hunk_offset_range = unstaged_hunk.buffer_range.to_offset(buffer);319 prev_unstaged_hunk_buffer_end = unstaged_hunk_offset_range.end;
321 prev_unstaged_hunk_base_text_offset = unstaged_hunk.diff_base_byte_range.end;320
322 prev_unstaged_hunk_buffer_offset = unstaged_hunk_offset_range.end;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 }
323 332
324 index_start = index_start.min(unstaged_hunk.diff_base_byte_range.start);333 // If any unstaged hunks were merged, then subsequent pending hunks may
325 buffer_offset_range.start = buffer_offset_range334 // now overlap this hunk. Merge them.
326 .start335 if let Some(next_pending_hunk) = pending_hunks_iter.peek() {
327 .min(unstaged_hunk_offset_range.start);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 }
328 344
329 unstaged_hunk_cursor.next(buffer);345 break;
330 }346 }
331 347
332 let end_overshoot = buffer_offset_range348 let end_overshoot = buffer_offset_range
333 .end349 .end
334 .saturating_sub(prev_unstaged_hunk_buffer_offset);350 .saturating_sub(prev_unstaged_hunk_buffer_end);
335 let index_end = prev_unstaged_hunk_base_text_offset + end_overshoot;351 let index_end = prev_unstaged_hunk_base_text_end + end_overshoot;
336 352 let index_byte_range = index_start..index_end;
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);
341 353
342 let replacement_text = if stage {354 let replacement_text = if stage {
343 log::debug!("stage hunk {:?}", buffer_offset_range);355 log::debug!("stage hunk {:?}", buffer_offset_range);
@@ -351,8 +363,9 @@ impl BufferDiffInner { · 7 unmodified lines
351 .collect::<String>()363 .collect::<String>()
352 };364 };
353 365
354 edits.push((index_range, replacement_text));366 edits.push((index_byte_range, replacement_text));
355 }367 }
368 drop(pending_hunks_iter);
356 369
357 #[cfg(debug_assertions)] // invariants: non-overlapping and sorted370 #[cfg(debug_assertions)] // invariants: non-overlapping and sorted
358 {371 {
@@ -1649,6 +1662,75 @@ mod tests { · 1290 unmodified lines
1649 "1662 "
1650 .unindent(),1663 .unindent(),
1651 },1664 },
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 },
1652 ];1734 ];
1653 1735
1654 for example in table {1736 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