git: Flatten the collection and index by ID

Jakub Konka created

Change summary

crates/git_ui/src/git_panel.rs             |   8 +
crates/project/src/git_store.rs            | 118 ++++++++++++-----------
crates/project/src/git_store/pending_op.rs |  74 ++++++--------
3 files changed, 100 insertions(+), 100 deletions(-)

Detailed changes

crates/git_ui/src/git_panel.rs 🔗

@@ -1304,7 +1304,13 @@ impl GitPanel {
         });
 
         let repository = active_repository.read(cx);
-        dbg!(&repository.snapshot().pending_ops_by_path);
+        for entry in &entries {
+            println!(
+                "{:?} -> {:?}",
+                &entry.repo_path,
+                repository.snapshot().pending_ops_for_path(&entry.repo_path)
+            );
+        }
         self.update_counts(repository);
         cx.notify();
 

crates/project/src/git_store.rs 🔗

@@ -45,7 +45,7 @@ use language::{
     proto::{deserialize_version, serialize_version},
 };
 use parking_lot::Mutex;
-use pending_op::{PendingOp, PendingOps, Status as PendingOpStatus};
+use pending_op::{PendingOp, PendingOpId, PendingOpStatus};
 use postage::stream::Stream as _;
 use rpc::{
     AnyProtoClient, TypedEnvelope,
@@ -250,7 +250,7 @@ pub struct MergeDetails {
 pub struct RepositorySnapshot {
     pub id: RepositoryId,
     pub statuses_by_path: SumTree<StatusEntry>,
-    pub pending_ops_by_path: SumTree<PendingOps>,
+    pub pending_ops: SumTree<PendingOp>,
     pub work_directory_abs_path: Arc<Path>,
     pub path_style: PathStyle,
     pub branch: Option<Branch>,
@@ -2906,7 +2906,7 @@ impl RepositorySnapshot {
         Self {
             id,
             statuses_by_path: Default::default(),
-            pending_ops_by_path: Default::default(),
+            pending_ops: Default::default(),
             work_directory_abs_path,
             branch: None,
             head_commit: None,
@@ -3034,11 +3034,16 @@ impl RepositorySnapshot {
             .cloned()
     }
 
-    pub fn pending_ops_for_path(&self, path: &RepoPath) -> Option<SumTree<PendingOp>> {
-        self.pending_ops_by_path
-            .get(&PathKey(path.0.clone()), ())
-            .map(|ops| &ops.ops)
-            .cloned()
+    pub fn pending_op_by_id(&self, id: PendingOpId) -> Option<PendingOp> {
+        self.pending_ops.get(&id, ()).cloned()
+    }
+
+    pub fn pending_ops_for_path(&self, path: &RepoPath) -> Vec<PendingOp> {
+        self.pending_ops
+            .filter::<_, PathKey>((), |sum| sum.max_path == path.0)
+            .into_iter()
+            .map(Clone::clone)
+            .collect()
     }
 
     pub fn abs_path_to_repo_path(&self, abs_path: &Path) -> Option<RepoPath> {
@@ -3776,10 +3781,20 @@ impl Repository {
             _ => None,
         };
 
-        let entries_cloned = entries.clone();
+        let mut ids = Vec::with_capacity(entries.len());
 
-        for entry in &entries_cloned {
-            self.push_pending_op_for_path(entry, PendingOpStatus::Staged);
+        for entry in &entries {
+            let id = self.snapshot.pending_ops.summary().item_summary.max_id + 1;
+            self.snapshot.pending_ops.push(
+                PendingOp {
+                    repo_path: entry.clone(),
+                    id,
+                    status: PendingOpStatus::Staged,
+                    finished: false,
+                },
+                (),
+            );
+            ids.push(id);
         }
 
         cx.spawn(async move |this, cx| {
@@ -3819,25 +3834,16 @@ 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, ());
-            //     }
-            // })?;
+            this.update(cx, |this, _| {
+                for id in ids {
+                    if let Some(mut op) = this.snapshot.pending_op_by_id(id) {
+                        op.finished = true;
+                        this.snapshot
+                            .pending_ops
+                            .edit(vec![sum_tree::Edit::Insert(op)], ());
+                    }
+                }
+            })?;
 
             Ok(())
         })
@@ -3864,8 +3870,20 @@ impl Repository {
             _ => None,
         };
 
+        let mut ids = Vec::with_capacity(entries.len());
+
         for entry in &entries {
-            self.push_pending_op_for_path(entry, PendingOpStatus::Unstaged);
+            let id = self.snapshot.pending_ops.summary().item_summary.max_id + 1;
+            self.snapshot.pending_ops.push(
+                PendingOp {
+                    repo_path: entry.clone(),
+                    id,
+                    status: PendingOpStatus::Unstaged,
+                    finished: false,
+                },
+                (),
+            );
+            ids.push(id);
         }
 
         cx.spawn(async move |this, cx| {
@@ -3905,6 +3923,17 @@ impl Repository {
             })?
             .await??;
 
+            this.update(cx, |this, _| {
+                for id in ids {
+                    if let Some(mut op) = this.snapshot.pending_op_by_id(id) {
+                        op.finished = true;
+                        this.snapshot
+                            .pending_ops
+                            .edit(vec![sum_tree::Edit::Insert(op)], ());
+                    }
+                }
+            })?;
+
             Ok(())
         })
     }
@@ -5123,29 +5152,6 @@ impl Repository {
     pub fn barrier(&mut self) -> oneshot::Receiver<()> {
         self.send_job(None, |_, _| async {})
     }
-
-    fn push_pending_op_for_path(&mut self, path: &RepoPath, status: PendingOpStatus) {
-        let mut ops = Vec::new();
-        let existing_ops = self.snapshot.pending_ops_for_path(path);
-        let id = existing_ops
-            .as_ref()
-            .map(|ops| ops.summary().max_id)
-            .unwrap_or(0)
-            + 1;
-        if let Some(existing_ops) = existing_ops {
-            ops.append(&mut existing_ops.items(()));
-        }
-        ops.push(PendingOp {
-            id,
-            status,
-            finished: false,
-        });
-        let edit = sum_tree::Edit::Insert(PendingOps {
-            repo_path: path.clone(),
-            ops: SumTree::from_iter(ops, ()),
-        });
-        self.snapshot.pending_ops_by_path.edit(vec![edit], ());
-    }
 }
 
 fn get_permalink_in_rust_registry_src(
@@ -5395,7 +5401,7 @@ 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();
+    let pending_ops = prev_snapshot.pending_ops.clone();
 
     if merge_heads_changed {
         events.push(RepositoryEvent::MergeHeadsChanged);
@@ -5422,7 +5428,7 @@ async fn compute_snapshot(
     let snapshot = RepositorySnapshot {
         id,
         statuses_by_path,
-        pending_ops_by_path,
+        pending_ops,
         work_directory_abs_path,
         path_style: prev_snapshot.path_style,
         scan_id: prev_snapshot.scan_id + 1,

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

@@ -1,9 +1,10 @@
 use git::repository::RepoPath;
-use sum_tree::{ContextLessSummary, Dimension, Item, KeyedItem, NoSummary, SumTree};
-use worktree::{PathKey, PathSummary};
+use std::ops::Add;
+use sum_tree::{ContextLessSummary, Dimension, Item, KeyedItem};
+use worktree::PathSummary;
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum Status {
+pub enum PendingOpStatus {
     Staged,
     Unstaged,
     Reverted,
@@ -12,54 +13,33 @@ pub enum Status {
 
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct PendingOp {
-    pub id: u16,
-    pub status: Status,
+    pub repo_path: RepoPath,
+    pub id: PendingOpId,
+    pub status: PendingOpStatus,
     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>,
+    pub max_id: PendingOpId,
+    pub staged_count: usize,
+    pub unstaged_count: usize,
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
-pub struct PendingOpId(u16);
+pub struct PendingOpId(pub usize);
 
-impl Item for PendingOps {
-    type Summary = PathSummary<NoSummary>;
+impl Item for PendingOp {
+    type Summary = PathSummary<PendingOpSummary>;
 
     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,
+            item_summary: PendingOpSummary {
+                max_id: self.id,
+                staged_count: (self.status == PendingOpStatus::Staged) as usize,
+                unstaged_count: (self.status == PendingOpStatus::Unstaged) as usize,
+            },
         }
     }
 }
@@ -67,7 +47,7 @@ impl Item for PendingOp {
 impl ContextLessSummary for PendingOpSummary {
     fn zero() -> Self {
         Self {
-            max_id: 0,
+            max_id: PendingOpId(0),
             staged_count: 0,
             unstaged_count: 0,
         }
@@ -84,16 +64,24 @@ impl KeyedItem for PendingOp {
     type Key = PendingOpId;
 
     fn key(&self) -> Self::Key {
-        PendingOpId(self.id)
+        self.id
     }
 }
 
-impl Dimension<'_, PendingOpSummary> for PendingOpId {
+impl Dimension<'_, PathSummary<PendingOpSummary>> for PendingOpId {
     fn zero(_cx: ()) -> Self {
         Self(0)
     }
 
-    fn add_summary(&mut self, summary: &PendingOpSummary, _cx: ()) {
-        self.0 = summary.max_id;
+    fn add_summary(&mut self, summary: &PathSummary<PendingOpSummary>, _cx: ()) {
+        *self = summary.item_summary.max_id;
+    }
+}
+
+impl Add<usize> for PendingOpId {
+    type Output = PendingOpId;
+
+    fn add(self, rhs: usize) -> Self::Output {
+        Self(self.0 + rhs)
     }
 }