git: Prevent panic when updating excerpts in split diff (cherry-pick #49725) (#49731)

Cole Miller , Jakub Konka , and Eric created

When using the split view, if the project diff inserts excerpts for the
same buffer using two different `PathKey`s, we panic inside
`LhsEditor::update_path_excerpts_from_rhs` because of an implicit
assumption that `excerpts_for_path()` and `excerpts_for_buffer()` return
the same number of values. This PR addresses that by ignoring any
excerpts for the given buffer that have a different path key from the
provided one.

Release Notes:

- Fixed a crash when using the split diff view.

Co-authored-by: Jakub Konka <kubkon@jakubkonka.com>
Co-authored-by: Eric <eric@zed.dev>

Change summary

crates/editor/src/split.rs | 62 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 59 insertions(+), 3 deletions(-)

Detailed changes

crates/editor/src/split.rs 🔗

@@ -2033,6 +2033,7 @@ impl LhsEditor {
         let new = rhs_multibuffer
             .excerpts_for_buffer(main_buffer.remote_id(), lhs_cx)
             .into_iter()
+            .filter(|(id, _)| rhs_excerpt_ids.contains(&id))
             .map(|(_, excerpt_range)| {
                 let point_range_to_base_text_point_range = |range: Range<Point>| {
                     let start = diff_snapshot
@@ -2076,14 +2077,14 @@ impl LhsEditor {
             let mut current_group = Vec::new();
             let mut last_id = None;
 
-            for (i, &lhs_id) in lhs_result.excerpt_ids.iter().enumerate() {
+            for (lhs_id, rhs_id) in lhs_result.excerpt_ids.iter().zip(rhs_excerpt_ids) {
                 if last_id == Some(lhs_id) {
-                    current_group.push(rhs_excerpt_ids[i]);
+                    current_group.push(rhs_id);
                 } else {
                     if !current_group.is_empty() {
                         groups.push(current_group);
                     }
-                    current_group = vec![rhs_excerpt_ids[i]];
+                    current_group = vec![rhs_id];
                     last_id = Some(lhs_id);
                 }
             }
@@ -2144,6 +2145,7 @@ mod tests {
     use rand::rngs::StdRng;
     use settings::{DiffViewStyle, SettingsStore};
     use ui::{VisualContext as _, div, px};
+    use util::rel_path::rel_path;
     use workspace::Workspace;
 
     use crate::SplittableEditor;
@@ -5857,4 +5859,58 @@ mod tests {
             );
         });
     }
+
+    #[gpui::test]
+    async fn test_two_path_keys_for_one_buffer(cx: &mut gpui::TestAppContext) {
+        use multi_buffer::PathKey;
+        use rope::Point;
+        use unindent::Unindent as _;
+
+        let (editor, mut cx) = init_test(cx, SoftWrap::None, DiffViewStyle::Split).await;
+
+        let base_text = "
+            aaa
+            bbb
+            ccc
+        "
+        .unindent();
+        let current_text = "
+            aaa
+            bbb modified
+            ccc
+        "
+        .unindent();
+
+        let (buffer, diff) = buffer_with_diff(&base_text, &current_text, &mut cx);
+
+        let path_key_1 = PathKey {
+            sort_prefix: Some(0),
+            path: rel_path("file1.txt").into(),
+        };
+        let path_key_2 = PathKey {
+            sort_prefix: Some(1),
+            path: rel_path("file1.txt").into(),
+        };
+
+        editor.update(cx, |editor, cx| {
+            editor.set_excerpts_for_path(
+                path_key_1.clone(),
+                buffer.clone(),
+                vec![Point::new(0, 0)..Point::new(1, 0)],
+                0,
+                diff.clone(),
+                cx,
+            );
+            editor.set_excerpts_for_path(
+                path_key_2.clone(),
+                buffer.clone(),
+                vec![Point::new(1, 0)..buffer.read(cx).max_point()],
+                1,
+                diff.clone(),
+                cx,
+            );
+        });
+
+        cx.run_until_parked();
+    }
 }