Fix panic when expanding a deletion hunk with blame open (#26130)

Cole Miller created

Closes #26118

Release Notes:

- Fixed a panic when expanding diff hunks while git blame is open

Change summary

crates/collab/src/tests/editor_tests.rs | 12 +++++++
crates/editor/src/git/blame.rs          | 42 ++++++++++++++++++++++----
2 files changed, 47 insertions(+), 7 deletions(-)

Detailed changes

crates/collab/src/tests/editor_tests.rs 🔗

@@ -2027,6 +2027,15 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
         .unwrap()
         .downcast::<Editor>()
         .unwrap();
+    let buffer_id_b = editor_b.update(cx_b, |editor_b, cx| {
+        editor_b
+            .buffer()
+            .read(cx)
+            .as_singleton()
+            .unwrap()
+            .read(cx)
+            .remote_id()
+    });
 
     // client_b now requests git blame for the open buffer
     editor_b.update_in(cx_b, |editor_b, window, cx| {
@@ -2045,6 +2054,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
                     &(0..4)
                         .map(|row| RowInfo {
                             buffer_row: Some(row),
+                            buffer_id: Some(buffer_id_b),
                             ..Default::default()
                         })
                         .collect::<Vec<_>>(),
@@ -2092,6 +2102,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
                     &(0..4)
                         .map(|row| RowInfo {
                             buffer_row: Some(row),
+                            buffer_id: Some(buffer_id_b),
                             ..Default::default()
                         })
                         .collect::<Vec<_>>(),
@@ -2127,6 +2138,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
                     &(0..4)
                         .map(|row| RowInfo {
                             buffer_row: Some(row),
+                            buffer_id: Some(buffer_id_b),
                             ..Default::default()
                         })
                         .collect::<Vec<_>>(),

crates/editor/src/git/blame.rs 🔗

@@ -195,9 +195,12 @@ impl GitBlame {
     ) -> impl 'a + Iterator<Item = Option<BlameEntry>> {
         self.sync(cx);
 
+        let buffer_id = self.buffer_snapshot.remote_id();
         let mut cursor = self.entries.cursor::<u32>(&());
         rows.into_iter().map(move |info| {
-            let row = info.buffer_row?;
+            let row = info
+                .buffer_row
+                .filter(|_| info.buffer_id == Some(buffer_id))?;
             cursor.seek_forward(&row, Bias::Right, &());
             cursor.item()?.blame.clone()
         })
@@ -535,6 +538,7 @@ mod tests {
     use serde_json::json;
     use settings::SettingsStore;
     use std::{cmp, env, ops::Range, path::Path};
+    use text::BufferId;
     use unindent::Unindent as _;
     use util::{path, RandomCharIter};
 
@@ -552,16 +556,18 @@ mod tests {
     #[track_caller]
     fn assert_blame_rows(
         blame: &mut GitBlame,
+        buffer_id: BufferId,
         rows: Range<u32>,
         expected: Vec<Option<BlameEntry>>,
         cx: &mut Context<GitBlame>,
     ) {
-        assert_eq!(
+        pretty_assertions::assert_eq!(
             blame
                 .blame_for_rows(
                     &rows
                         .map(|row| RowInfo {
                             buffer_row: Some(row),
+                            buffer_id: Some(buffer_id),
                             ..Default::default()
                         })
                         .collect::<Vec<_>>(),
@@ -694,6 +700,7 @@ mod tests {
             })
             .await
             .unwrap();
+        let buffer_id = buffer.update(cx, |buffer, _| buffer.remote_id());
 
         let git_blame = cx.new(|cx| GitBlame::new(buffer.clone(), project, false, true, cx));
 
@@ -701,12 +708,13 @@ mod tests {
 
         git_blame.update(cx, |blame, cx| {
             // All lines
-            assert_eq!(
+            pretty_assertions::assert_eq!(
                 blame
                     .blame_for_rows(
                         &(0..8)
                             .map(|buffer_row| RowInfo {
                                 buffer_row: Some(buffer_row),
+                                buffer_id: Some(buffer_id),
                                 ..Default::default()
                             })
                             .collect::<Vec<_>>(),
@@ -725,12 +733,13 @@ mod tests {
                 ]
             );
             // Subset of lines
-            assert_eq!(
+            pretty_assertions::assert_eq!(
                 blame
                     .blame_for_rows(
                         &(1..4)
                             .map(|buffer_row| RowInfo {
                                 buffer_row: Some(buffer_row),
+                                buffer_id: Some(buffer_id),
                                 ..Default::default()
                             })
                             .collect::<Vec<_>>(),
@@ -744,12 +753,13 @@ mod tests {
                 ]
             );
             // Subset of lines, with some not displayed
-            assert_eq!(
+            pretty_assertions::assert_eq!(
                 blame
                     .blame_for_rows(
                         &[
                             RowInfo {
                                 buffer_row: Some(1),
+                                buffer_id: Some(buffer_id),
                                 ..Default::default()
                             },
                             Default::default(),
@@ -800,6 +810,7 @@ mod tests {
             })
             .await
             .unwrap();
+        let buffer_id = buffer.update(cx, |buffer, _| buffer.remote_id());
 
         let git_blame = cx.new(|cx| GitBlame::new(buffer.clone(), project, false, true, cx));
 
@@ -810,6 +821,7 @@ mod tests {
             // lines.
             assert_blame_rows(
                 blame,
+                buffer_id,
                 0..4,
                 vec![
                     Some(blame_entry("1b1b1b", 0..4)),
@@ -828,6 +840,7 @@ mod tests {
         git_blame.update(cx, |blame, cx| {
             assert_blame_rows(
                 blame,
+                buffer_id,
                 0..2,
                 vec![None, Some(blame_entry("1b1b1b", 0..4))],
                 cx,
@@ -840,6 +853,7 @@ mod tests {
         git_blame.update(cx, |blame, cx| {
             assert_blame_rows(
                 blame,
+                buffer_id,
                 1..4,
                 vec![
                     None,
@@ -852,7 +866,13 @@ mod tests {
 
         // Before we insert a newline at the end, sanity check:
         git_blame.update(cx, |blame, cx| {
-            assert_blame_rows(blame, 3..4, vec![Some(blame_entry("1b1b1b", 0..4))], cx);
+            assert_blame_rows(
+                blame,
+                buffer_id,
+                3..4,
+                vec![Some(blame_entry("1b1b1b", 0..4))],
+                cx,
+            );
         });
         // Insert a newline at the end
         buffer.update(cx, |buffer, cx| {
@@ -862,6 +882,7 @@ mod tests {
         git_blame.update(cx, |blame, cx| {
             assert_blame_rows(
                 blame,
+                buffer_id,
                 3..5,
                 vec![Some(blame_entry("1b1b1b", 0..4)), None],
                 cx,
@@ -870,7 +891,13 @@ mod tests {
 
         // Before we insert a newline at the start, sanity check:
         git_blame.update(cx, |blame, cx| {
-            assert_blame_rows(blame, 2..3, vec![Some(blame_entry("1b1b1b", 0..4))], cx);
+            assert_blame_rows(
+                blame,
+                buffer_id,
+                2..3,
+                vec![Some(blame_entry("1b1b1b", 0..4))],
+                cx,
+            );
         });
 
         // Usage example
@@ -882,6 +909,7 @@ mod tests {
         git_blame.update(cx, |blame, cx| {
             assert_blame_rows(
                 blame,
+                buffer_id,
                 2..4,
                 vec![None, Some(blame_entry("1b1b1b", 0..4))],
                 cx,