Run `git2::Repository::find_remote` in the background (#44092)

Cole Miller and cameron created

We were seeing this hog the main thread.

Release Notes:

- N/A

---------

Co-authored-by: cameron <cameron.studdstreet@gmail.com>

Change summary

crates/fs/src/fake_git_repo.rs                            |  4 
crates/git/src/repository.rs                              | 53 +++++---
crates/git_hosting_providers/src/git_hosting_providers.rs |  4 
crates/project/src/git_store.rs                           |  8 
crates/project/src/telemetry_snapshot.rs                  |  2 
5 files changed, 43 insertions(+), 28 deletions(-)

Detailed changes

crates/fs/src/fake_git_repo.rs 🔗

@@ -152,8 +152,8 @@ impl GitRepository for FakeGitRepository {
         })
     }
 
-    fn remote_url(&self, _name: &str) -> Option<String> {
-        None
+    fn remote_url(&self, _name: &str) -> BoxFuture<'_, Option<String>> {
+        async move { None }.boxed()
     }
 
     fn diff_tree(&self, _request: DiffTreeType) -> BoxFuture<'_, Result<TreeDiff>> {

crates/git/src/repository.rs 🔗

@@ -420,7 +420,7 @@ pub trait GitRepository: Send + Sync {
     ) -> BoxFuture<'_, anyhow::Result<()>>;
 
     /// Returns the URL of the remote with the given name.
-    fn remote_url(&self, name: &str) -> Option<String>;
+    fn remote_url(&self, name: &str) -> BoxFuture<'_, Option<String>>;
 
     /// Resolve a list of refs to SHAs.
     fn revparse_batch(&self, revs: Vec<String>) -> BoxFuture<'_, Result<Vec<Option<String>>>>;
@@ -1085,10 +1085,16 @@ impl GitRepository for RealGitRepository {
             .boxed()
     }
 
-    fn remote_url(&self, name: &str) -> Option<String> {
-        let repo = self.repository.lock();
-        let remote = repo.find_remote(name).ok()?;
-        remote.url().map(|url| url.to_string())
+    fn remote_url(&self, name: &str) -> BoxFuture<'_, Option<String>> {
+        let repo = self.repository.clone();
+        let name = name.to_owned();
+        self.executor
+            .spawn(async move {
+                let repo = repo.lock();
+                let remote = repo.find_remote(&name).ok()?;
+                remote.url().map(|url| url.to_string())
+            })
+            .boxed()
     }
 
     fn revparse_batch(&self, revs: Vec<String>) -> BoxFuture<'_, Result<Vec<Option<String>>>> {
@@ -1465,23 +1471,30 @@ impl GitRepository for RealGitRepository {
     fn blame(&self, path: RepoPath, content: Rope) -> BoxFuture<'_, Result<crate::blame::Blame>> {
         let working_directory = self.working_directory();
         let git_binary_path = self.any_git_binary_path.clone();
+        let executor = self.executor.clone();
 
-        let remote_url = self
-            .remote_url("upstream")
-            .or_else(|| self.remote_url("origin"));
-
-        self.executor
-            .spawn(async move {
-                crate::blame::Blame::for_path(
-                    &git_binary_path,
-                    &working_directory?,
-                    &path,
-                    &content,
-                    remote_url,
-                )
+        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
+                })
                 .await
-            })
-            .boxed()
+        }
+        .boxed()
     }
 
     fn file_history(&self, path: RepoPath) -> BoxFuture<'_, Result<FileHistory>> {

crates/git_hosting_providers/src/git_hosting_providers.rs 🔗

@@ -33,11 +33,11 @@ pub fn init(cx: &mut App) {
 ///
 /// These require information from the Git repository to construct, so their
 /// registration is deferred until we have a Git repository initialized.
-pub fn register_additional_providers(
+pub async fn register_additional_providers(
     provider_registry: Arc<GitHostingProviderRegistry>,
     repository: Arc<dyn GitRepository>,
 ) {
-    let Some(origin_url) = repository.remote_url("origin") else {
+    let Some(origin_url) = repository.remote_url("origin").await else {
         return;
     };
 

crates/project/src/git_store.rs 🔗

@@ -1130,6 +1130,7 @@ impl GitStore {
                     RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
                         let origin_url = backend
                             .remote_url(&remote)
+                            .await
                             .with_context(|| format!("remote \"{remote}\" not found"))?;
 
                         let sha = backend.head_sha().await.context("reading HEAD SHA")?;
@@ -5447,7 +5448,8 @@ impl Repository {
                 git_hosting_providers::register_additional_providers(
                     git_hosting_provider_registry,
                     state.backend.clone(),
-                );
+                )
+                .await;
             }
             let state = RepositoryState::Local(state);
             let mut jobs = VecDeque::new();
@@ -6052,8 +6054,8 @@ async fn compute_snapshot(
     }
 
     // Used by edit prediction data collection
-    let remote_origin_url = backend.remote_url("origin");
-    let remote_upstream_url = backend.remote_url("upstream");
+    let remote_origin_url = backend.remote_url("origin").await;
+    let remote_upstream_url = backend.remote_url("upstream").await;
 
     let snapshot = RepositorySnapshot {
         id,

crates/project/src/telemetry_snapshot.rs 🔗

@@ -96,7 +96,7 @@ impl TelemetryWorktreeSnapshot {
                                 };
                             };
 
-                            let remote_url = backend.remote_url("origin");
+                            let remote_url = backend.remote_url("origin").await;
                             let head_sha = backend.head_sha().await;
                             let diff = backend.diff(DiffType::HeadToWorktree).await.ok();