Fix bug where git statuses would not be initialized on startup

Mikayla Maki and max created

move git status queries to be on entry creation

co-authored-by: max <max@zed.dev>

Change summary

crates/project/src/worktree.rs | 92 +++++++++++++++++++----------------
1 file changed, 50 insertions(+), 42 deletions(-)

Detailed changes

crates/project/src/worktree.rs 🔗

@@ -1822,6 +1822,14 @@ impl LocalSnapshot {
         self.git_repositories.get(&repo.work_directory.0)
     }
 
+    pub(crate) fn local_repo_for_path(
+        &self,
+        path: &Path,
+    ) -> Option<(RepositoryWorkDirectory, &LocalRepositoryEntry)> {
+        let (path, repo) = self.repository_and_work_directory_for_path(path)?;
+        Some((path, self.git_repositories.get(&repo.work_directory_id())?))
+    }
+
     pub(crate) fn repo_for_metadata(
         &self,
         path: &Path,
@@ -1997,6 +2005,8 @@ impl LocalSnapshot {
                 },
             );
 
+            self.scan_statuses(repo_lock.deref(), &work_directory);
+
             drop(repo_lock);
 
             self.git_repositories.insert(
@@ -2016,10 +2026,12 @@ impl LocalSnapshot {
         &mut self,
         repo_ptr: &dyn GitRepository,
         work_directory: &RepositoryWorkDirectory,
-        path: &Path,
     ) {
         let mut edits = vec![];
-        for mut entry in self.descendent_entries(false, false, path).cloned() {
+        for mut entry in self
+            .descendent_entries(false, false, &work_directory.0)
+            .cloned()
+        {
             let Ok(repo_path) = entry.path.strip_prefix(&work_directory.0) else {
                 continue;
             };
@@ -2754,6 +2766,7 @@ impl BackgroundScanner {
             let mut state = self.state.lock();
             state.snapshot.completed_scan_id = state.snapshot.scan_id;
         }
+
         self.send_status_update(false, None);
 
         // Process any any FS events that occurred while performing the initial scan.
@@ -2813,7 +2826,7 @@ impl BackgroundScanner {
 
             if let Some(paths) = paths {
                 for path in paths {
-                    self.reload_git_status(&path, &mut *snapshot, self.fs.as_ref());
+                    self.reload_git_repo(&path, &mut *snapshot, self.fs.as_ref());
                 }
             }
 
@@ -2940,14 +2953,18 @@ impl BackgroundScanner {
         let mut new_jobs: Vec<Option<ScanJob>> = Vec::new();
         let mut ignore_stack = job.ignore_stack.clone();
         let mut new_ignore = None;
-        let (root_abs_path, root_char_bag, next_entry_id) = {
+        let (root_abs_path, root_char_bag, next_entry_id, repository) = {
             let snapshot = &self.state.lock().snapshot;
             (
                 snapshot.abs_path().clone(),
                 snapshot.root_char_bag,
                 self.next_entry_id.clone(),
+                snapshot
+                    .local_repo_for_path(&job.path)
+                    .map(|(work_dir, repo)| (work_dir, repo.clone())),
             )
         };
+
         let mut child_paths = self.fs.read_dir(&job.abs_path).await?;
         while let Some(child_abs_path) = child_paths.next().await {
             let child_abs_path: Arc<Path> = match child_abs_path {
@@ -3040,6 +3057,13 @@ impl BackgroundScanner {
                 }
             } else {
                 child_entry.is_ignored = ignore_stack.is_abs_path_ignored(&child_abs_path, false);
+
+                if let Some((repo_path, repo)) = &repository {
+                    if let Ok(path) = child_path.strip_prefix(&repo_path.0) {
+                        child_entry.git_status =
+                            repo.repo_ptr.lock().status(&RepoPath(path.into()));
+                    }
+                }
             }
 
             new_entries.push(child_entry);
@@ -3117,6 +3141,7 @@ impl BackgroundScanner {
                     let ignore_stack = state
                         .snapshot
                         .ignore_stack_for_abs_path(&abs_path, metadata.is_dir);
+
                     let mut fs_entry = Entry::new(
                         path.clone(),
                         &metadata,
@@ -3124,6 +3149,16 @@ impl BackgroundScanner {
                         state.snapshot.root_char_bag,
                     );
                     fs_entry.is_ignored = ignore_stack.is_all();
+
+                    if !fs_entry.is_dir() {
+                        if let Some((work_dir, repo)) = state.snapshot.local_repo_for_path(&path) {
+                            if let Ok(path) = path.strip_prefix(work_dir.0) {
+                                fs_entry.git_status =
+                                    repo.repo_ptr.lock().status(&RepoPath(path.into()))
+                            }
+                        }
+                    }
+
                     state.insert_entry(fs_entry, self.fs.as_ref());
 
                     if let Some(scan_queue_tx) = &scan_queue_tx {
@@ -3183,7 +3218,7 @@ impl BackgroundScanner {
         Some(())
     }
 
-    fn reload_git_status(
+    fn reload_git_repo(
         &self,
         path: &Path,
         snapshot: &mut LocalSnapshot,
@@ -3230,32 +3265,7 @@ impl BackgroundScanner {
                     entry.branch = branch.map(Into::into);
                 });
 
-            snapshot.scan_statuses(repo.deref(), &work_dir, &work_dir.0);
-        } else {
-            if snapshot
-                .entry_for_path(&path)
-                .map(|entry| entry.is_ignored)
-                .unwrap_or(false)
-            {
-                return None;
-            }
-
-            let repo = snapshot.repository_for_path(&path)?;
-            let work_directory = &repo.work_directory(snapshot)?;
-            let work_dir_id = repo.work_directory.clone();
-            let (local_repo, git_dir_scan_id) =
-                snapshot.git_repositories.update(&work_dir_id, |entry| {
-                    (entry.repo_ptr.clone(), entry.git_dir_scan_id)
-                })?;
-
-            // Short circuit if we've already scanned everything
-            if git_dir_scan_id == scan_id {
-                return None;
-            }
-
-            let repo_ptr = local_repo.lock();
-
-            snapshot.scan_statuses(repo_ptr.deref(), &work_directory, path);
+            snapshot.scan_statuses(repo.deref(), &work_dir);
         }
 
         Some(())
@@ -5270,6 +5280,15 @@ mod tests {
             )
             .await;
 
+            fs.set_status_for_repo(
+                &Path::new("/root/.git"),
+                &[
+                    (Path::new("a/b/c1.txt"), GitFileStatus::Added),
+                    (Path::new("a/d/e2.txt"), GitFileStatus::Modified),
+                    (Path::new("g/h2.txt"), GitFileStatus::Conflict),
+                ],
+            );
+
             let http_client = FakeHttpClient::with_404_response();
             let client = cx.read(|cx| Client::new(http_client, cx));
             let tree = Worktree::local(
@@ -5285,17 +5304,6 @@ mod tests {
 
             cx.foreground().run_until_parked();
 
-            fs.set_status_for_repo(
-                &Path::new("/root/.git"),
-                &[
-                    (Path::new("a/b/c1.txt"), GitFileStatus::Added),
-                    (Path::new("a/d/e2.txt"), GitFileStatus::Modified),
-                    (Path::new("g/h2.txt"), GitFileStatus::Conflict),
-                ],
-            );
-
-            cx.foreground().run_until_parked();
-
             let snapshot = tree.read_with(cx, |tree, _| tree.snapshot());
 
             assert_eq!(