git: Add PendingOperations container to RepositorySnapshot

Jakub Konka created

Change summary

crates/project/src/git_store.rs | 103 ++++++++++++++++++++++++++++++++++
1 file changed, 101 insertions(+), 2 deletions(-)

Detailed changes

crates/project/src/git_store.rs 🔗

@@ -31,8 +31,8 @@ use git::{
     },
     stash::{GitStash, StashEntry},
     status::{
-        DiffTreeType, FileStatus, GitSummary, StatusCode, TrackedStatus, TreeDiff, TreeDiffStatus,
-        UnmergedStatus, UnmergedStatusCode,
+        DiffTreeType, FileStatus, GitSummary, StageStatus, StatusCode, TrackedStatus, TreeDiff,
+        TreeDiffStatus, UnmergedStatus, UnmergedStatusCode,
     },
 };
 use gpui::{
@@ -244,10 +244,90 @@ pub struct MergeDetails {
     pub heads: Vec<Option<SharedString>>,
 }
 
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum PendingOperationStatus {
+    Staged,
+    Unstaged,
+    Reverted,
+    Unchanged,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct PendingOperations {
+    repo_path: RepoPath,
+    // TODO: move this into StatusEntry
+    staging: StageStatus,
+    ops: Vec<PendingOperation>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct PendingOperation {
+    id: usize,
+    finished: bool,
+    status: PendingOperationStatus,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct PendingOperationsSummary {
+    finished: bool,
+    status: PendingOperationStatus,
+    count: usize,
+}
+
+impl Default for PendingOperationsSummary {
+    fn default() -> Self {
+        Self {
+            finished: false,
+            status: PendingOperationStatus::Unstaged,
+            count: 0,
+        }
+    }
+}
+
+impl sum_tree::ContextLessSummary for PendingOperationsSummary {
+    fn zero() -> Self {
+        Default::default()
+    }
+
+    fn add_summary(&mut self, rhs: &Self) {
+        self.finished = self.finished || rhs.finished;
+        self.count += rhs.count;
+        self.status = rhs.status;
+    }
+}
+
+impl sum_tree::Item for PendingOperations {
+    type Summary = PathSummary<PendingOperationsSummary>;
+
+    fn summary(&self, _: <Self::Summary as sum_tree::Summary>::Context<'_>) -> Self::Summary {
+        let mut item_summary = PendingOperationsSummary {
+            count: self.ops.len(),
+            ..Default::default()
+        };
+        if let Some(op) = self.ops.last() {
+            item_summary.finished = op.finished;
+            item_summary.status = op.status;
+        }
+        PathSummary {
+            max_path: self.repo_path.0.clone(),
+            item_summary,
+        }
+    }
+}
+
+impl sum_tree::KeyedItem for PendingOperations {
+    type Key = PathKey;
+
+    fn key(&self) -> Self::Key {
+        PathKey(self.repo_path.0.clone())
+    }
+}
+
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct RepositorySnapshot {
     pub id: RepositoryId,
     pub statuses_by_path: SumTree<StatusEntry>,
+    pub pending_ops_by_path: SumTree<PendingOperations>,
     pub work_directory_abs_path: Arc<Path>,
     pub path_style: PathStyle,
     pub branch: Option<Branch>,
@@ -2903,6 +2983,7 @@ impl RepositorySnapshot {
         Self {
             id,
             statuses_by_path: Default::default(),
+            pending_ops_by_path: Default::default(),
             work_directory_abs_path,
             branch: None,
             head_commit: None,
@@ -3030,6 +3111,21 @@ impl RepositorySnapshot {
             .cloned()
     }
 
+    pub fn pending_ops(&self) -> impl Iterator<Item = PendingOperations> + '_ {
+        self.pending_ops_by_path.iter().cloned()
+    }
+
+    pub fn pending_ops_summary(&self) -> PendingOperationsSummary {
+        self.pending_ops_by_path.summary().item_summary
+    }
+
+    pub fn pending_ops_for_path(&self, path: &RepoPath) -> Vec<PendingOperation> {
+        self.pending_ops_by_path
+            .get(&PathKey(path.0.clone()), ())
+            .map(|ops| ops.ops.clone())
+            .unwrap_or(Vec::new())
+    }
+
     pub fn abs_path_to_repo_path(&self, abs_path: &Path) -> Option<RepoPath> {
         Self::abs_path_to_repo_path_inner(&self.work_directory_abs_path, abs_path, self.path_style)
     }
@@ -5331,6 +5427,8 @@ async fn compute_snapshot(
         MergeDetails::load(&backend, &statuses_by_path, &prev_snapshot).await?;
     log::debug!("new merge details (changed={merge_heads_changed:?}): {merge_details:?}");
 
+    let pending_ops_by_path = prev_snapshot.pending_ops_by_path.clone();
+
     if merge_heads_changed {
         events.push(RepositoryEvent::MergeHeadsChanged);
     }
@@ -5356,6 +5454,7 @@ async fn compute_snapshot(
     let snapshot = RepositorySnapshot {
         id,
         statuses_by_path,
+        pending_ops_by_path,
         work_directory_abs_path,
         path_style: prev_snapshot.path_style,
         scan_id: prev_snapshot.scan_id + 1,