git_panel: Fix buffer header checkbox not showing partially staged files (#42718)

Jakub Konka created

Release Notes:

- Fixed buffer header controls (staging checkbox) not showing partially
staged files

Change summary

crates/git_ui/src/git_panel.rs              | 41 +++++++++++-----------
crates/project/src/git_store.rs             | 11 ++---
crates/project/src/git_store/branch_diff.rs |  6 --
crates/project/src/project_tests.rs         |  6 +-
crates/project_panel/src/project_panel.rs   |  6 --
5 files changed, 30 insertions(+), 40 deletions(-)

Detailed changes

crates/git_ui/src/git_panel.rs 🔗

@@ -409,17 +409,10 @@ impl GitPanel {
                     }
                     GitStoreEvent::RepositoryUpdated(
                         _,
-                        RepositoryEvent::StatusesChanged { full_scan: true }
+                        RepositoryEvent::StatusesChanged
                         | RepositoryEvent::BranchChanged
                         | RepositoryEvent::MergeHeadsChanged,
                         true,
-                    ) => {
-                        this.schedule_update(window, cx);
-                    }
-                    GitStoreEvent::RepositoryUpdated(
-                        _,
-                        RepositoryEvent::StatusesChanged { full_scan: false },
-                        true,
                     )
                     | GitStoreEvent::RepositoryAdded
                     | GitStoreEvent::RepositoryRemoved(_) => {
@@ -1224,14 +1217,18 @@ impl GitPanel {
         let Some(active_repository) = self.active_repository.as_ref() else {
             return;
         };
+        let repo = active_repository.read(cx);
         let (stage, repo_paths) = match entry {
             GitListEntry::Status(status_entry) => {
                 let repo_paths = vec![status_entry.clone()];
-                let stage = if active_repository
-                    .read(cx)
+                let stage = if repo
                     .pending_ops_for_path(&status_entry.repo_path)
                     .map(|ops| ops.staging() || ops.staged())
-                    .unwrap_or(status_entry.status.staging().has_staged())
+                    .or_else(|| {
+                        repo.status_for_path(&status_entry.repo_path)
+                            .map(|status| status.status.staging().has_staged())
+                    })
+                    .unwrap_or(status_entry.staging.has_staged())
                 {
                     if let Some(op) = self.bulk_staging.clone()
                         && op.anchor == status_entry.repo_path
@@ -1247,13 +1244,12 @@ impl GitPanel {
             }
             GitListEntry::Header(section) => {
                 let goal_staged_state = !self.header_state(section.header).selected();
-                let repository = active_repository.read(cx);
                 let entries = self
                     .entries
                     .iter()
                     .filter_map(|entry| entry.status_entry())
                     .filter(|status_entry| {
-                        section.contains(status_entry, repository)
+                        section.contains(status_entry, repo)
                             && status_entry.staging.as_bool() != Some(goal_staged_state)
                     })
                     .cloned()
@@ -3659,13 +3655,18 @@ impl GitPanel {
         let ix = self.entry_by_path(&repo_path, cx)?;
         let entry = self.entries.get(ix)?;
 
-        let is_staging_or_staged = if let Some(status_entry) = entry.status_entry() {
-            repo.pending_ops_for_path(&repo_path)
-                .map(|ops| ops.staging() || ops.staged())
-                .unwrap_or(status_entry.staging.has_staged())
-        } else {
-            false
-        };
+        let is_staging_or_staged = repo
+            .pending_ops_for_path(&repo_path)
+            .map(|ops| ops.staging() || ops.staged())
+            .or_else(|| {
+                repo.status_for_path(&repo_path)
+                    .and_then(|status| status.status.staging().as_bool())
+            })
+            .or_else(|| {
+                entry
+                    .status_entry()
+                    .and_then(|entry| entry.staging.as_bool())
+            });
 
         let checkbox = Checkbox::new("stage-file", is_staging_or_staged.into())
             .disabled(!self.has_write_access(cx))

crates/project/src/git_store.rs 🔗

@@ -312,10 +312,7 @@ pub enum RepositoryState {
 
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub enum RepositoryEvent {
-    StatusesChanged {
-        // TODO could report which statuses changed here
-        full_scan: bool,
-    },
+    StatusesChanged,
     MergeHeadsChanged,
     BranchChanged,
     StashEntriesChanged,
@@ -4989,7 +4986,7 @@ impl Repository {
             )
             .collect::<Vec<_>>();
         if !edits.is_empty() {
-            cx.emit(RepositoryEvent::StatusesChanged { full_scan: true });
+            cx.emit(RepositoryEvent::StatusesChanged);
         }
         self.snapshot.statuses_by_path.edit(edits, ());
         if update.is_last_update {
@@ -5343,7 +5340,7 @@ impl Repository {
                     }
 
                     if !changed_path_statuses.is_empty() {
-                        cx.emit(RepositoryEvent::StatusesChanged { full_scan: false });
+                        cx.emit(RepositoryEvent::StatusesChanged);
                         this.snapshot
                             .statuses_by_path
                             .edit(changed_path_statuses, ());
@@ -5725,7 +5722,7 @@ async fn compute_snapshot(
     }
 
     if statuses_by_path != prev_snapshot.statuses_by_path {
-        events.push(RepositoryEvent::StatusesChanged { full_scan: true })
+        events.push(RepositoryEvent::StatusesChanged)
     }
 
     // Useful when branch is None in detached head state

crates/project/src/git_store/branch_diff.rs 🔗

@@ -63,11 +63,7 @@ impl BranchDiff {
             window,
             move |this, _git_store, event, _window, cx| match event {
                 GitStoreEvent::ActiveRepositoryChanged(_)
-                | GitStoreEvent::RepositoryUpdated(
-                    _,
-                    RepositoryEvent::StatusesChanged { full_scan: _ },
-                    true,
-                )
+                | GitStoreEvent::RepositoryUpdated(_, RepositoryEvent::StatusesChanged, true)
                 | GitStoreEvent::ConflictsUpdated => {
                     cx.emit(BranchDiffEvent::FileListChanged);
                     *this.update_needed.borrow_mut() = ();

crates/project/src/project_tests.rs 🔗

@@ -9575,7 +9575,7 @@ async fn test_ignored_dirs_events(cx: &mut gpui::TestAppContext) {
     assert_eq!(
         repository_updates.lock().drain(..).collect::<Vec<_>>(),
         vec![
-            RepositoryEvent::StatusesChanged { full_scan: true },
+            RepositoryEvent::StatusesChanged,
             RepositoryEvent::MergeHeadsChanged,
         ],
         "Initial worktree scan should produce a repo update event"
@@ -9743,8 +9743,8 @@ async fn test_odd_events_for_ignored_dirs(
         vec![
             RepositoryEvent::MergeHeadsChanged,
             RepositoryEvent::BranchChanged,
-            RepositoryEvent::StatusesChanged { full_scan: false },
-            RepositoryEvent::StatusesChanged { full_scan: false },
+            RepositoryEvent::StatusesChanged,
+            RepositoryEvent::StatusesChanged,
         ],
         "Initial worktree scan should produce a repo update event"
     );

crates/project_panel/src/project_panel.rs 🔗

@@ -505,11 +505,7 @@ impl ProjectPanel {
                 &git_store,
                 window,
                 |this, _, event, window, cx| match event {
-                    GitStoreEvent::RepositoryUpdated(
-                        _,
-                        RepositoryEvent::StatusesChanged { full_scan: _ },
-                        _,
-                    )
+                    GitStoreEvent::RepositoryUpdated(_, RepositoryEvent::StatusesChanged, _)
                     | GitStoreEvent::RepositoryAdded
                     | GitStoreEvent::RepositoryRemoved(_) => {
                         this.update_visible_entries(None, false, false, window, cx);