git: Don't call `git2::Repository::find_remote` for every blamed buffer (#44107)

Cole Miller created

We already store the remote URLs for `origin` and `upstream` in the
`RepositorySnapshot`, so just use that data. Follow-up to #44092.

Release Notes:

- N/A

Change summary

crates/collab/migrations.sqlite/20221109000000_test_schema.sql                      |  2 
crates/collab/migrations/20251203234258_add_remote_urls_to_project_repositories.sql |  2 
crates/collab/src/db/queries/projects.rs                                            |  6 
crates/collab/src/db/queries/rooms.rs                                               |  2 
crates/collab/src/db/tables/project_repository.rs                                   |  2 
crates/collab/src/tests/editor_tests.rs                                             |  5 
crates/editor/src/git/blame.rs                                                      | 22 
crates/git/src/blame.rs                                                             |  8 
crates/git/src/repository.rs                                                        | 31 
crates/project/src/git_store.rs                                                     | 19 
crates/proto/proto/git.proto                                                        |  4 
11 files changed, 51 insertions(+), 52 deletions(-)

Detailed changes

crates/collab/src/db/queries/projects.rs 🔗

@@ -362,6 +362,8 @@ impl Database {
                                 entry_ids: ActiveValue::set("[]".into()),
                                 head_commit_details: ActiveValue::set(None),
                                 merge_message: ActiveValue::set(None),
+                                remote_upstream_url: ActiveValue::set(None),
+                                remote_origin_url: ActiveValue::set(None),
                             }
                         }),
                     )
@@ -511,6 +513,8 @@ impl Database {
                     serde_json::to_string(&update.current_merge_conflicts).unwrap(),
                 )),
                 merge_message: ActiveValue::set(update.merge_message.clone()),
+                remote_upstream_url: ActiveValue::set(update.remote_upstream_url.clone()),
+                remote_origin_url: ActiveValue::set(update.remote_origin_url.clone()),
             })
             .on_conflict(
                 OnConflict::columns([
@@ -1005,6 +1009,8 @@ impl Database {
                         is_last_update: true,
                         merge_message: db_repository_entry.merge_message,
                         stash_entries: Vec::new(),
+                        remote_upstream_url: db_repository_entry.remote_upstream_url.clone(),
+                        remote_origin_url: db_repository_entry.remote_origin_url.clone(),
                     });
                 }
             }

crates/collab/src/db/queries/rooms.rs 🔗

@@ -796,6 +796,8 @@ impl Database {
                             is_last_update: true,
                             merge_message: db_repository.merge_message,
                             stash_entries: Vec::new(),
+                            remote_upstream_url: db_repository.remote_upstream_url.clone(),
+                            remote_origin_url: db_repository.remote_origin_url.clone(),
                         });
                     }
                 }

crates/collab/src/db/tables/project_repository.rs 🔗

@@ -22,6 +22,8 @@ pub struct Model {
     pub branch_summary: Option<String>,
     // A JSON object representing the current Head commit values
     pub head_commit_details: Option<String>,
+    pub remote_upstream_url: Option<String>,
+    pub remote_origin_url: Option<String>,
 }
 
 #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]

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

@@ -3518,7 +3518,6 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
         .into_iter()
         .map(|(sha, message)| (sha.parse().unwrap(), message.into()))
         .collect(),
-        remote_url: Some("git@github.com:zed-industries/zed.git".to_string()),
     };
     client_a.fs().set_blame_for_repo(
         Path::new(path!("/my-repo/.git")),
@@ -3603,10 +3602,6 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
             for (idx, (buffer, entry)) in entries.iter().flatten().enumerate() {
                 let details = blame.details_for_entry(*buffer, entry).unwrap();
                 assert_eq!(details.message, format!("message for idx-{}", idx));
-                assert_eq!(
-                    details.permalink.unwrap().to_string(),
-                    format!("https://github.com/zed-industries/zed/commit/{}", entry.sha)
-                );
             }
         });
     });

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

@@ -508,7 +508,19 @@ impl GitBlame {
                     let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
 
                     let blame_buffer = project.blame_buffer(&buffer, None, cx);
-                    Some(async move { (id, snapshot, buffer_edits, blame_buffer.await) })
+                    let remote_url = project
+                        .git_store()
+                        .read(cx)
+                        .repository_and_path_for_buffer_id(buffer.read(cx).remote_id(), cx)
+                        .and_then(|(repo, _)| {
+                            repo.read(cx)
+                                .remote_upstream_url
+                                .clone()
+                                .or(repo.read(cx).remote_origin_url.clone())
+                        });
+                    Some(
+                        async move { (id, snapshot, buffer_edits, blame_buffer.await, remote_url) },
+                    )
                 })
                 .collect::<Vec<_>>()
         });
@@ -524,13 +536,9 @@ impl GitBlame {
                             .await;
                         let mut res = vec![];
                         let mut errors = vec![];
-                        for (id, snapshot, buffer_edits, blame) in blame {
+                        for (id, snapshot, buffer_edits, blame, remote_url) in blame {
                             match blame {
-                                Ok(Some(Blame {
-                                    entries,
-                                    messages,
-                                    remote_url,
-                                })) => {
+                                Ok(Some(Blame { entries, messages })) => {
                                     let entries = build_blame_entry_sum_tree(
                                         entries,
                                         snapshot.max_point().row,

crates/git/src/blame.rs 🔗

@@ -19,7 +19,6 @@ pub use git2 as libgit;
 pub struct Blame {
     pub entries: Vec<BlameEntry>,
     pub messages: HashMap<Oid, String>,
-    pub remote_url: Option<String>,
 }
 
 #[derive(Clone, Debug, Default)]
@@ -36,7 +35,6 @@ impl Blame {
         working_directory: &Path,
         path: &RepoPath,
         content: &Rope,
-        remote_url: Option<String>,
     ) -> Result<Self> {
         let output = run_git_blame(git_binary, working_directory, path, content).await?;
         let mut entries = parse_git_blame(&output)?;
@@ -53,11 +51,7 @@ impl Blame {
             .await
             .context("failed to get commit messages")?;
 
-        Ok(Self {
-            entries,
-            messages,
-            remote_url,
-        })
+        Ok(Self { entries, messages })
     }
 }
 

crates/git/src/repository.rs 🔗

@@ -1494,28 +1494,17 @@ impl GitRepository for RealGitRepository {
         let git_binary_path = self.any_git_binary_path.clone();
         let executor = self.executor.clone();
 
-        async move {
-            let remote_url = if let Some(remote_url) = self.remote_url("upstream").await {
-                Some(remote_url)
-            } else if let Some(remote_url) = self.remote_url("origin").await {
-                Some(remote_url)
-            } else {
-                None
-            };
-            executor
-                .spawn(async move {
-                    crate::blame::Blame::for_path(
-                        &git_binary_path,
-                        &working_directory?,
-                        &path,
-                        &content,
-                        remote_url,
-                    )
-                    .await
-                })
+        executor
+            .spawn(async move {
+                crate::blame::Blame::for_path(
+                    &git_binary_path,
+                    &working_directory?,
+                    &path,
+                    &content,
+                )
                 .await
-        }
-        .boxed()
+            })
+            .boxed()
     }
 
     fn file_history(&self, path: RepoPath) -> BoxFuture<'_, Result<FileHistory>> {

crates/project/src/git_store.rs 🔗

@@ -3296,6 +3296,8 @@ impl RepositorySnapshot {
                 .iter()
                 .map(stash_to_proto)
                 .collect(),
+            remote_upstream_url: self.remote_upstream_url.clone(),
+            remote_origin_url: self.remote_origin_url.clone(),
         }
     }
 
@@ -3365,6 +3367,8 @@ impl RepositorySnapshot {
                 .iter()
                 .map(stash_to_proto)
                 .collect(),
+            remote_upstream_url: self.remote_upstream_url.clone(),
+            remote_origin_url: self.remote_origin_url.clone(),
         }
     }
 
@@ -5395,6 +5399,8 @@ impl Repository {
             cx.emit(RepositoryEvent::StashEntriesChanged)
         }
         self.snapshot.stash_entries = new_stash_entries;
+        self.snapshot.remote_upstream_url = update.remote_upstream_url;
+        self.snapshot.remote_origin_url = update.remote_origin_url;
 
         let edits = update
             .removed_statuses
@@ -5954,11 +5960,7 @@ fn serialize_blame_buffer_response(blame: Option<git::blame::Blame>) -> proto::B
         .collect::<Vec<_>>();
 
     proto::BlameBufferResponse {
-        blame_response: Some(proto::blame_buffer_response::BlameResponse {
-            entries,
-            messages,
-            remote_url: blame.remote_url,
-        }),
+        blame_response: Some(proto::blame_buffer_response::BlameResponse { entries, messages }),
     }
 }
 
@@ -5995,11 +5997,7 @@ fn deserialize_blame_buffer_response(
         .filter_map(|message| Some((git::Oid::from_bytes(&message.oid).ok()?, message.message)))
         .collect::<HashMap<_, _>>();
 
-    Some(Blame {
-        entries,
-        messages,
-        remote_url: response.remote_url,
-    })
+    Some(Blame { entries, messages })
 }
 
 fn branch_to_proto(branch: &git::repository::Branch) -> proto::Branch {
@@ -6147,7 +6145,6 @@ async fn compute_snapshot(
         events.push(RepositoryEvent::BranchChanged);
     }
 
-    // Used by edit prediction data collection
     let remote_origin_url = backend.remote_url("origin").await;
     let remote_upstream_url = backend.remote_url("upstream").await;
 

crates/proto/proto/git.proto 🔗

@@ -124,6 +124,8 @@ message UpdateRepository {
     optional GitCommitDetails head_commit_details = 11;
     optional string merge_message = 12;
     repeated StashEntry stash_entries = 13;
+    optional string remote_upstream_url = 14;
+    optional string remote_origin_url = 15;
 }
 
 message RemoveRepository {
@@ -500,8 +502,8 @@ message BlameBufferResponse {
     message BlameResponse {
         repeated BlameEntry entries = 1;
         repeated CommitMessage messages = 2;
-        optional string remote_url = 4;
         reserved 3;
+        reserved 4;
     }
 
     optional BlameResponse blame_response = 5;