Release Notes: - Git: Fix crash when staging a hunk that overlaps multiple unstaged hunks. ---------
Showing the changes to crates/buffer_diff/src/buffer_diff.rs (1 of 1 file). Show all
Unified · Split
| @@ -3,9 +3,7 @@ use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as · 2 unmodified lines | |||
| 3 | use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task}; | 3 | use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task}; |
| 4 | use language::{Language, LanguageRegistry}; | 4 | use language::{Language, LanguageRegistry}; |
| 5 | use rope::Rope; | 5 | use rope::Rope; |
| 6 | use std::cmp::Ordering; | 6 | use std::{cmp::Ordering, future::Future, iter, mem, ops::Range, sync::Arc}; |
| 7 | use std::mem; | ||
| 8 | use std::{future::Future, iter, ops::Range, sync::Arc}; | ||
| 9 | use sum_tree::SumTree; | 7 | use sum_tree::SumTree; |
| 10 | use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point, ToOffset as _}; | 8 | use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point, ToOffset as _}; |
| 11 | use util::ResultExt; | 9 | use util::ResultExt; |
| @@ -189,7 +187,7 @@ impl BufferDiffSnapshot { · 177 unmodified lines | |||
| 189 | impl BufferDiffInner { | 187 | impl 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 edits | 288 | // 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_range | 314 | // Merge this hunk with any overlapping unstaged hunks. |
| 316 | .start | 315 | 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_range | 334 | // now overlap this hunk. Merge them. |
| 326 | .start | 335 | 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_range | 348 | let end_overshoot = buffer_offset_range |
| 333 | .end | 349 | .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 sorted | 370 | #[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 { |