From d5d2b1fc85f25e393c2255ca4a3528c13365593e Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 1 Nov 2025 09:20:28 +0100 Subject: [PATCH] git: Store a single PendingOperation in SumTree But now the summary will return a list of PendingOperations per RepoPath --- crates/git_ui/src/git_panel.rs | 2 + crates/project/src/git_store.rs | 162 +++++++++------------ crates/project/src/git_store/pending_op.rs | 99 +++++++++++++ 3 files changed, 166 insertions(+), 97 deletions(-) create mode 100644 crates/project/src/git_store/pending_op.rs diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 0c43058c067aa9b6abcc333e58f7e4933d783b73..3d4a2ff45ff9cb936c038b77bb6fee0d9b81bdff 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/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(); diff --git a/crates/project/src/git_store.rs b/crates/project/src/git_store.rs index 105c43597ceca2db66063d642eed2780e5c7b26c..125f58446f97a27e1af98650ac2b50e74574a8c9 100644 --- a/crates/project/src/git_store.rs +++ b/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>, } -#[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, -} - -#[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; - - fn summary(&self, _: ::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, - pub pending_ops_by_path: SumTree, + pub pending_ops_by_path: SumTree, pub work_directory_abs_path: Arc, pub path_style: PathStyle, pub branch: Option, @@ -3155,19 +3078,11 @@ impl RepositorySnapshot { .cloned() } - pub fn pending_ops(&self) -> impl Iterator + '_ { - 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 { + pub fn pending_ops_for_path(&self, path: &RepoPath) -> Option> { self.pending_ops_by_path .get(&PathKey(path.0.clone()), ()) - .map(|ops| ops.ops.clone()) - .unwrap_or(Vec::new()) + .map(|ops| &ops.ops) + .cloned() } pub fn abs_path_to_repo_path(&self, abs_path: &Path) -> Option { @@ -3885,7 +3800,7 @@ impl Repository { } pub fn stage_entries( - &self, + &mut self, entries: Vec, cx: &mut Context, ) -> Task> { @@ -3905,6 +3820,12 @@ impl Repository { _ => None, }; + let entries_cloned = entries.clone(); + + for entry in &entries_cloned { + self.push_pending_op_for_path(entry, PendingOpStatus::Staged); + } + cx.spawn(async move |this, cx| { for save_task in save_tasks { save_task.await?; @@ -3942,12 +3863,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, cx: &mut Context, ) -> Task> { @@ -3967,6 +3908,10 @@ impl Repository { _ => None, }; + for entry in &entries { + self.push_pending_op_for_path(entry, PendingOpStatus::Unstaged); + } + cx.spawn(async move |this, cx| { for save_task in save_tasks { save_task.await?; @@ -4008,7 +3953,7 @@ impl Repository { }) } - pub fn stage_all(&self, cx: &mut Context) -> Task> { + pub fn stage_all(&mut self, cx: &mut Context) -> Task> { let to_stage = self .cached_status() .filter(|entry| !entry.status.staging().is_fully_staged()) @@ -4017,7 +3962,7 @@ impl Repository { self.stage_entries(to_stage, cx) } - pub fn unstage_all(&self, cx: &mut Context) -> Task> { + pub fn unstage_all(&mut self, cx: &mut Context) -> Task> { let to_unstage = self .cached_status() .filter(|entry| entry.status.staging().has_staged()) @@ -5279,6 +5224,29 @@ 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( diff --git a/crates/project/src/git_store/pending_op.rs b/crates/project/src/git_store/pending_op.rs new file mode 100644 index 0000000000000000000000000000000000000000..776d7841e5730bd3895c4fc1afdea30d318fd98a --- /dev/null +++ b/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, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct PendingOpId(u16); + +impl Item for PendingOps { + type Summary = PathSummary; + + 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; + } +}