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 | 3 | use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task}; |
| 4 | 4 | use language::{Language, LanguageRegistry}; |
| 5 | 5 | 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}; | |
| 9 | 7 | use sum_tree::SumTree; |
| 10 | 8 | use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point, ToOffset as _}; |
| 11 | 9 | use util::ResultExt; |
| @@ -189,7 +187,7 @@ impl BufferDiffSnapshot { · 177 unmodified lines | ||
| 189 | 187 | impl BufferDiffInner { |
| 190 | 188 | /// Returns the new index text and new pending hunks. |
| 191 | 189 | fn stage_or_unstage_hunks_impl( |
| 192 | − &mut self, | |
| 190 | + &self, | |
| 193 | 191 | unstaged_diff: &Self, |
| 194 | 192 | stage: bool, |
| 195 | 193 | hunks: &[DiffHunk], |
| @@ -261,7 +259,6 @@ impl BufferDiffInner { · 65 unmodified lines | ||
| 261 | 259 | old_pending_hunks.next(buffer); |
| 262 | 260 | } |
| 263 | 261 | |
| 264 | − // merge into pending hunks | |
| 265 | 262 | if (stage && secondary_status == DiffHunkSecondaryStatus::NoSecondaryHunk) |
| 266 | 263 | || (!stage && secondary_status == DiffHunkSecondaryStatus::HasSecondaryHunk) |
| 267 | 264 | { |
| @@ -288,56 +285,71 @@ impl BufferDiffInner { · 20 unmodified lines | ||
| 288 | 285 | let mut unstaged_hunk_cursor = unstaged_diff.hunks.cursor::<DiffHunkSummary>(buffer); |
| 289 | 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 | 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 | 294 | buffer_range, |
| 298 | 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() { | |
| 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); | |
| 308 | 306 | } |
| 309 | 307 | |
| 308 | + // Find where this hunk is in the index if it doesn't overlap | |
| 310 | 309 | 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 | + } | |
| 323 | 332 | |
| 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 | + } | |
| 328 | 344 | |
| 329 | − unstaged_hunk_cursor.next(buffer); | |
| 345 | + break; | |
| 330 | 346 | } |
| 331 | 347 | |
| 332 | 348 | let end_overshoot = buffer_offset_range |
| 333 | 349 | .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; | |
| 341 | 353 | |
| 342 | 354 | let replacement_text = if stage { |
| 343 | 355 | log::debug!("stage hunk {:?}", buffer_offset_range); |
| @@ -351,8 +363,9 @@ impl BufferDiffInner { · 7 unmodified lines | ||
| 351 | 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 | 370 | #[cfg(debug_assertions)] // invariants: non-overlapping and sorted |
| 358 | 371 | { |
| @@ -1649,6 +1662,75 @@ mod tests { · 1290 unmodified lines | ||
| 1649 | 1662 | " |
| 1650 | 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 | 1736 | for example in table { |