git: Store a single PendingOperation in SumTree

Jakub Konka created

But now the summary will return a list of PendingOperations per RepoPath

Change summary

crates/git_ui/src/git_panel.rs             |   2 
crates/project/src/git_store.rs            | 156 ++++++++---------------
crates/project/src/git_store/pending_op.rs |  99 +++++++++++++++
3 files changed, 156 insertions(+), 101 deletions(-)

Detailed changes

crates/git_ui/src/git_panel.rs 🔗

@@ -1302,7 +1302,9 @@ impl GitPanel {
             entries: entries.clone(),
             finished: false,
         });
+
         let repository = active_repository.read(cx);
+        dbg!(&repository.snapshot().pending_ops_by_path);
         self.update_counts(repository);
         cx.notify();
 

crates/project/src/git_store.rs 🔗

@@ -1,6 +1,7 @@
 pub mod branch_diff;
 mod conflict_set;
 pub mod git_traversal;
+pub mod pending_op;
 
 use crate::{
     ProjectEnvironment, ProjectItem, ProjectPath,
@@ -31,8 +32,8 @@ use git::{
     },
     stash::{GitStash, StashEntry},
     status::{
-        DiffTreeType, FileStatus, GitSummary, StageStatus, StatusCode, TrackedStatus, TreeDiff,
-        TreeDiffStatus, UnmergedStatus, UnmergedStatusCode,
+        DiffTreeType, FileStatus, GitSummary, StatusCode, TrackedStatus, TreeDiff, TreeDiffStatus,
+        UnmergedStatus, UnmergedStatusCode,
     },
 };
 use gpui::{
@@ -44,6 +45,7 @@ use language::{
     proto::{deserialize_version, serialize_version},
 };
 use parking_lot::Mutex;
+use pending_op::{PendingOp, PendingOps, Status as PendingOpStatus};
 use postage::stream::Stream as _;
 use rpc::{
     AnyProtoClient, TypedEnvelope,
@@ -244,90 +246,11 @@ 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 pending_ops_by_path: SumTree<PendingOps>,
     pub work_directory_abs_path: Arc<Path>,
     pub path_style: PathStyle,
     pub branch: Option<Branch>,
@@ -3111,21 +3034,6 @@ 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)
     }
@@ -3841,7 +3749,7 @@ impl Repository {
     }
 
     pub fn stage_entries(
-        &self,
+        &mut self,
         entries: Vec<RepoPath>,
         cx: &mut Context<Self>,
     ) -> Task<anyhow::Result<()>> {
@@ -3861,6 +3769,32 @@ impl Repository {
             _ => None,
         };
 
+        let entries_cloned = entries.clone();
+
+        for entry in &entries_cloned {
+            let mut ops = Vec::new();
+            let mut id: u16 = 1;
+            if let Some(inner) = self
+                .snapshot
+                .pending_ops_by_path
+                .get(&PathKey(entry.0.clone()), ())
+                .map(|ops| &ops.ops)
+            {
+                ops.append(&mut inner.items(()));
+                id += inner.summary().max_id;
+            }
+            ops.push(PendingOp {
+                id,
+                status: PendingOpStatus::Staged,
+                finished: false,
+            });
+            let edit = sum_tree::Edit::Insert(PendingOps {
+                repo_path: entry.clone(),
+                ops: SumTree::from_iter(ops, ()),
+            });
+            self.snapshot.pending_ops_by_path.edit(vec![edit], ());
+        }
+
         cx.spawn(async move |this, cx| {
             for save_task in save_tasks {
                 save_task.await?;
@@ -3898,12 +3832,32 @@ impl Repository {
             })?
             .await??;
 
+            // this.update(cx, |this, _| {
+            //     for entry in &entries_cloned {
+            //         let key = PathKey(entry.0.clone());
+            //         let Some(pending) = this.snapshot.pending_ops_by_path.get(&key) else { continue; };
+            //         let Some(mut pending) = this.snapshot.pending_ops_by_path.remove(&key, ())
+            //         else {
+            //             continue;
+            //         };
+            //         for p in &mut pending.ops {
+            //             if p.id == *op_id {
+            //                 p.finished = true;
+            //                 break;
+            //             }
+            //         }
+            //         this.snapshot
+            //             .pending_ops_by_path
+            //             .insert_or_replace(pending, ());
+            //     }
+            // })?;
+
             Ok(())
         })
     }
 
     pub fn unstage_entries(
-        &self,
+        &mut self,
         entries: Vec<RepoPath>,
         cx: &mut Context<Self>,
     ) -> Task<anyhow::Result<()>> {
@@ -3964,7 +3918,7 @@ impl Repository {
         })
     }
 
-    pub fn stage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
+    pub fn stage_all(&mut self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
         let to_stage = self
             .cached_status()
             .filter(|entry| !entry.status.staging().is_fully_staged())
@@ -3973,7 +3927,7 @@ impl Repository {
         self.stage_entries(to_stage, cx)
     }
 
-    pub fn unstage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
+    pub fn unstage_all(&mut self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
         let to_unstage = self
             .cached_status()
             .filter(|entry| entry.status.staging().has_staged())

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

@@ -0,0 +1,99 @@
+use git::repository::RepoPath;
+use sum_tree::{ContextLessSummary, Dimension, Item, KeyedItem, NoSummary, SumTree};
+use worktree::{PathKey, PathSummary};
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum Status {
+    Staged,
+    Unstaged,
+    Reverted,
+    Unchanged,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct PendingOp {
+    pub id: u16,
+    pub status: Status,
+    pub finished: bool,
+}
+
+#[derive(Clone, Debug)]
+pub struct PendingOpSummary {
+    pub max_id: u16,
+    pub staged_count: u32,
+    pub unstaged_count: u32,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct PendingOps {
+    pub repo_path: RepoPath,
+    pub ops: SumTree<PendingOp>,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct PendingOpId(u16);
+
+impl Item for PendingOps {
+    type Summary = PathSummary<NoSummary>;
+
+    fn summary(&self, _cx: ()) -> Self::Summary {
+        PathSummary {
+            max_path: self.repo_path.0.clone(),
+            item_summary: NoSummary,
+        }
+    }
+}
+
+impl KeyedItem for PendingOps {
+    type Key = PathKey;
+
+    fn key(&self) -> Self::Key {
+        PathKey(self.repo_path.0.clone())
+    }
+}
+
+impl Item for PendingOp {
+    type Summary = PendingOpSummary;
+
+    fn summary(&self, _cx: ()) -> Self::Summary {
+        PendingOpSummary {
+            max_id: self.id,
+            staged_count: (self.status == Status::Staged) as u32,
+            unstaged_count: (self.status == Status::Unstaged) as u32,
+        }
+    }
+}
+
+impl ContextLessSummary for PendingOpSummary {
+    fn zero() -> Self {
+        Self {
+            max_id: 0,
+            staged_count: 0,
+            unstaged_count: 0,
+        }
+    }
+
+    fn add_summary(&mut self, summary: &Self) {
+        self.max_id = summary.max_id;
+        self.staged_count += summary.staged_count;
+        self.unstaged_count += summary.unstaged_count;
+    }
+}
+
+impl KeyedItem for PendingOp {
+    type Key = PendingOpId;
+
+    fn key(&self) -> Self::Key {
+        PendingOpId(self.id)
+    }
+}
+
+impl Dimension<'_, PendingOpSummary> for PendingOpId {
+    fn zero(_cx: ()) -> Self {
+        Self(0)
+    }
+
+    fn add_summary(&mut self, summary: &PendingOpSummary, _cx: ()) {
+        self.0 = summary.max_id;
+    }
+}