diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index bc6ce873ae..8c3fe18607 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -7,7 +7,7 @@ use std::{ sync::Arc, time::Duration, }; -use worktree::GitEntry; +use worktree::StatusEntry; use git::repository::{GitFileStatus, RepoPath}; @@ -90,7 +90,7 @@ pub struct GitPanel { // not hidden by folding or such visible_entries: Vec<( WorktreeId, - Vec, + Vec, OnceCell>, )>, width: Option, @@ -232,7 +232,7 @@ impl GitPanel { } fn calculate_depth_and_difference( - entry: &GitEntry, + entry: &StatusEntry, visible_worktree_entries: &HashSet, ) -> (usize, usize) { let (depth, difference) = entry diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 359970a142..0052c81121 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -22,7 +22,6 @@ use editor::{ }; use file_icons::FileIcons; use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; -use git::repository::GitFileStatus; use gpui::{ actions, anchored, deferred, div, point, px, size, uniform_list, Action, AnyElement, AppContext, AssetSource, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent, Div, @@ -57,7 +56,7 @@ use workspace::{ }, OpenInTerminal, WeakItemHandle, Workspace, }; -use worktree::{Entry, ProjectEntryId, WorktreeId}; +use worktree::{Entry, GitEntry, ProjectEntryId, WorktreeId}; actions!( outline_panel, @@ -352,8 +351,7 @@ enum ExcerptOutlines { #[derive(Clone, Debug, PartialEq, Eq)] struct FoldedDirsEntry { worktree_id: WorktreeId, - entries: Vec, - git_file_statuses: Vec>, + entries: Vec, } // TODO: collapse the inner enums into panel entry @@ -581,10 +579,9 @@ impl OutlineEntry { #[derive(Debug, Clone, Eq)] struct FsEntryFile { worktree_id: WorktreeId, - entry: Entry, + entry: GitEntry, buffer_id: BufferId, excerpts: Vec, - git_status: Option, } impl PartialEq for FsEntryFile { @@ -604,8 +601,7 @@ impl Hash for FsEntryFile { #[derive(Debug, Clone, Eq)] struct FsEntryDirectory { worktree_id: WorktreeId, - entry: Entry, - git_status: Option, + entry: GitEntry, } impl PartialEq for FsEntryDirectory { @@ -2087,13 +2083,11 @@ impl OutlinePanel { }; let (item_id, label_element, icon) = match rendered_entry { FsEntry::File(FsEntryFile { - worktree_id, - entry, - git_status, - .. + worktree_id, entry, .. }) => { let name = self.entry_name(worktree_id, entry, cx); - let color = entry_git_aware_label_color(*git_status, entry.is_ignored, is_active); + let color = + entry_git_aware_label_color(entry.git_status, entry.is_ignored, is_active); let icon = if settings.file_icons { FileIcons::get_icon(&entry.path, cx) .map(|icon_path| Icon::from_path(icon_path).color(color).into_any_element()) @@ -2113,18 +2107,18 @@ impl OutlinePanel { icon.unwrap_or_else(empty_icon), ) } - FsEntry::Directory(FsEntryDirectory { - worktree_id, + FsEntry::Directory(directory) => { + let name = self.entry_name(&directory.worktree_id, &directory.entry, cx); - entry, - git_status, - }) => { - let name = self.entry_name(worktree_id, entry, cx); - - let is_expanded = !self - .collapsed_entries - .contains(&CollapsedEntry::Dir(*worktree_id, entry.id)); - let color = entry_git_aware_label_color(*git_status, entry.is_ignored, is_active); + let is_expanded = !self.collapsed_entries.contains(&CollapsedEntry::Dir( + directory.worktree_id, + directory.entry.id, + )); + let color = entry_git_aware_label_color( + directory.entry.git_status, + directory.entry.is_ignored, + is_active, + ); let icon = if settings.folder_icons { FileIcons::get_folder_icon(is_expanded, cx) } else { @@ -2133,7 +2127,7 @@ impl OutlinePanel { .map(Icon::from_path) .map(|icon| icon.color(color).into_any_element()); ( - ElementId::from(entry.id.to_proto() as usize), + ElementId::from(directory.entry.id.to_proto() as usize), HighlightedLabel::new( name, string_match @@ -2214,7 +2208,10 @@ impl OutlinePanel { .contains(&CollapsedEntry::Dir(folded_dir.worktree_id, dir.id)) }); let is_ignored = folded_dir.entries.iter().any(|entry| entry.is_ignored); - let git_status = folded_dir.git_file_statuses.first().cloned().flatten(); + let git_status = folded_dir + .entries + .first() + .and_then(|entry| entry.git_status); let color = entry_git_aware_label_color(git_status, is_ignored, is_active); let icon = if settings.folder_icons { FileIcons::get_folder_icon(is_expanded, cx) @@ -2520,7 +2517,7 @@ impl OutlinePanel { let mut processed_external_buffers = HashSet::default(); let mut new_worktree_entries = HashMap::< WorktreeId, - (worktree::Snapshot, HashMap), + (worktree::Snapshot, HashMap), >::default(); let mut worktree_excerpts = HashMap::< WorktreeId, @@ -2561,12 +2558,13 @@ impl OutlinePanel { match entry_id.and_then(|id| worktree.entry_for_id(id)).cloned() { Some(entry) => { - let mut traversal = worktree.traverse_from_path( - true, - true, - true, - entry.path.as_ref(), - ); + let entry = GitEntry { + git_status: worktree.status_for_file(&entry.path), + entry, + }; + let mut traversal = worktree + .traverse_from_path(true, true, true, entry.path.as_ref()) + .with_git_statuses(); let mut entries_to_add = HashMap::default(); worktree_excerpts @@ -2598,7 +2596,7 @@ impl OutlinePanel { .is_none(); if new_entry_added && traversal.back_to_parent() { if let Some(parent_entry) = traversal.entry() { - current_entry = parent_entry.clone(); + current_entry = parent_entry.to_owned(); continue; } } @@ -2636,15 +2634,14 @@ impl OutlinePanel { let mut entries = entries.into_values().collect::>(); // For a proper git status propagation, we have to keep the entries sorted lexicographically. entries.sort_by(|a, b| a.path.as_ref().cmp(b.path.as_ref())); - let statuses = worktree_snapshot.propagate_git_statuses(&entries); - let entries = entries.into_iter().zip(statuses).collect::>(); + worktree_snapshot.propagate_git_statuses(&mut entries); (worktree_id, entries) }) .flat_map(|(worktree_id, entries)| { { entries .into_iter() - .filter_map(|(entry, git_status)| { + .filter_map(|entry| { if auto_fold_dirs { if let Some(parent) = entry.path.parent() { let children = new_children_count @@ -2664,7 +2661,6 @@ impl OutlinePanel { Some(FsEntry::Directory(FsEntryDirectory { worktree_id, entry, - git_status, })) } else { let (buffer_id, excerpts) = worktree_excerpts @@ -2677,7 +2673,6 @@ impl OutlinePanel { buffer_id, entry, excerpts, - git_status, })) } }) @@ -3461,7 +3456,6 @@ impl OutlinePanel { FoldedDirsEntry { worktree_id: directory_entry.worktree_id, entries: vec![directory_entry.entry.clone()], - git_file_statuses: vec![directory_entry.git_status], }, )) }; @@ -3472,7 +3466,6 @@ impl OutlinePanel { FoldedDirsEntry { worktree_id: directory_entry.worktree_id, entries: vec![directory_entry.entry.clone()], - git_file_statuses: vec![directory_entry.git_status], }, )); } @@ -3715,7 +3708,6 @@ impl OutlinePanel { 1 => PanelEntry::Fs(FsEntry::Directory(FsEntryDirectory { worktree_id: folded_dirs_entry.worktree_id, entry: folded_dirs_entry.entries[0].clone(), - git_status: folded_dirs_entry.git_file_statuses[0], })), _ => entry, } @@ -3778,7 +3770,7 @@ impl OutlinePanel { fn dir_names_string( &self, - entries: &[Entry], + entries: &[GitEntry], worktree_id: WorktreeId, cx: &AppContext, ) -> String { @@ -4521,7 +4513,7 @@ impl OutlinePanel { fn buffers_inside_directory( &self, dir_worktree: WorktreeId, - dir_entry: &Entry, + dir_entry: &GitEntry, ) -> HashSet { if !dir_entry.is_dir() { debug_panic!("buffers_inside_directory called on a non-directory entry {dir_entry:?}"); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 0083bd5561..e6672d53e5 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -4456,11 +4456,13 @@ impl Completion { } } -pub fn sort_worktree_entries(entries: &mut [(Entry, Option)]) { +pub fn sort_worktree_entries(entries: &mut [impl AsRef]) { entries.sort_by(|entry_a, entry_b| { + let entry_a = entry_a.as_ref(); + let entry_b = entry_b.as_ref(); compare_paths( - (&entry_a.0.path, entry_a.0.is_file()), - (&entry_b.0.path, entry_b.0.is_file()), + (&entry_a.path, entry_a.is_file()), + (&entry_b.path, entry_b.is_file()), ) }); } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 20bab76334..bfbb0890e8 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -63,7 +63,7 @@ use workspace::{ notifications::{DetachAndPromptErr, NotifyTaskExt}, DraggedSelection, OpenInTerminal, PreviewTabsSettings, SelectedEntry, Workspace, }; -use worktree::CreatedEntry; +use worktree::{CreatedEntry, GitEntry, GitEntryRef}; const PROJECT_PANEL_KEY: &str = "ProjectPanel"; const NEW_ENTRY_ID: ProjectEntryId = ProjectEntryId::MAX; @@ -76,11 +76,7 @@ pub struct ProjectPanel { // An update loop that keeps incrementing/decrementing scroll offset while there is a dragged entry that's // hovered over the start/end of a list. hover_scroll_task: Option>, - visible_entries: Vec<( - WorktreeId, - Vec<(Entry, Option)>, - OnceCell>>, - )>, + visible_entries: Vec<(WorktreeId, Vec, OnceCell>>)>, /// Maps from leaf project entry ID to the currently selected ancestor. /// Relevant only for auto-fold dirs, where a single project panel entry may actually consist of several /// project entries (and all non-leaf nodes are guaranteed to be directories). @@ -893,7 +889,7 @@ impl ProjectPanel { let (worktree_id, worktree_entries, _) = &self.visible_entries[worktree_ix]; let selection = SelectedEntry { worktree_id: *worktree_id, - entry_id: worktree_entries[entry_ix].0.id, + entry_id: worktree_entries[entry_ix].id, }; self.selection = Some(selection); if cx.modifiers().shift { @@ -1366,6 +1362,7 @@ impl ProjectPanel { let mut siblings: Vec<_> = worktree .snapshot() .child_entries(parent_path) + .with_git_statuses() .filter(|sibling| { sibling.id == latest_entry.id || !marked_entries_in_worktree.contains(&&SelectedEntry { @@ -1373,14 +1370,13 @@ impl ProjectPanel { entry_id: sibling.id, }) }) - .map(|sibling| &(*sibling, None)) - .cloned() + .map(|entry| entry.to_owned()) .collect(); project::sort_worktree_entries(&mut siblings); let sibling_entry_index = siblings .iter() - .position(|sibling| sibling.0.id == latest_entry.0.id)?; + .position(|sibling| sibling.id == latest_entry.id)?; if let Some(next_sibling) = sibling_entry_index .checked_add(1) @@ -1388,7 +1384,7 @@ impl ProjectPanel { { return Some(SelectedEntry { worktree_id, - entry_id: next_sibling.0.id, + entry_id: next_sibling.id, }); } if let Some(prev_sibling) = sibling_entry_index @@ -1397,7 +1393,7 @@ impl ProjectPanel { { return Some(SelectedEntry { worktree_id, - entry_id: prev_sibling.0.id, + entry_id: prev_sibling.id, }); } // No neighbour sibling found, fall back to parent @@ -1491,7 +1487,7 @@ impl ProjectPanel { if let Some(entry) = worktree_entries.get(entry_ix) { let selection = SelectedEntry { worktree_id: *worktree_id, - entry_id: entry.0.id, + entry_id: entry.id, }; self.selection = Some(selection); if cx.modifiers().shift { @@ -1511,7 +1507,7 @@ impl ProjectPanel { let selection = self.find_entry( self.selection.as_ref(), true, - |entry, _, worktree_id| { + |entry, worktree_id| { (self.selection.is_none() || self.selection.is_some_and(|selection| { if selection.worktree_id == worktree_id { @@ -1541,7 +1537,7 @@ impl ProjectPanel { let selection = self.find_entry( self.selection.as_ref(), false, - |entry, _, worktree_id| { + |entry, worktree_id| { (self.selection.is_none() || self.selection.is_some_and(|selection| { if selection.worktree_id == worktree_id { @@ -1571,7 +1567,7 @@ impl ProjectPanel { let selection = self.find_entry( self.selection.as_ref(), true, - |entry, git_status, worktree_id| { + |entry, worktree_id| { (self.selection.is_none() || self.selection.is_some_and(|selection| { if selection.worktree_id == worktree_id { @@ -1581,7 +1577,9 @@ impl ProjectPanel { } })) && entry.is_file() - && git_status.is_some_and(|status| matches!(status, GitFileStatus::Modified)) + && entry + .git_status + .is_some_and(|status| matches!(status, GitFileStatus::Modified)) }, cx, ); @@ -1649,7 +1647,7 @@ impl ProjectPanel { let selection = self.find_entry( self.selection.as_ref(), true, - |entry, git_status, worktree_id| { + |entry, worktree_id| { (self.selection.is_none() || self.selection.is_some_and(|selection| { if selection.worktree_id == worktree_id { @@ -1659,7 +1657,9 @@ impl ProjectPanel { } })) && entry.is_file() - && git_status.is_some_and(|status| matches!(status, GitFileStatus::Modified)) + && entry + .git_status + .is_some_and(|status| matches!(status, GitFileStatus::Modified)) }, cx, ); @@ -2110,7 +2110,7 @@ impl ProjectPanel { { if *worktree_id == selection.worktree_id { for entry in worktree_entries { - if entry.0.id == selection.entry_id { + if entry.id == selection.entry_id { return Some((worktree_index, entry_index, visible_entries_index)); } else { visible_entries_index += 1; @@ -2308,7 +2308,7 @@ impl ProjectPanel { } let mut visible_worktree_entries = Vec::new(); - let mut entry_iter = snapshot.entries(true, 0); + let mut entry_iter = snapshot.entries(true, 0).with_git_statuses(); let mut auto_folded_ancestors = vec![]; while let Some(entry) = entry_iter.entry() { if auto_collapse_dirs && entry.kind.is_dir() { @@ -2350,7 +2350,7 @@ impl ProjectPanel { } } auto_folded_ancestors.clear(); - visible_worktree_entries.push(entry.clone()); + visible_worktree_entries.push(entry.to_owned()); let precedes_new_entry = if let Some(new_entry_id) = new_entry_parent_id { entry.id == new_entry_id || { self.ancestors.get(&entry.id).map_or(false, |entries| { @@ -2364,24 +2364,27 @@ impl ProjectPanel { false }; if precedes_new_entry { - visible_worktree_entries.push(Entry { - id: NEW_ENTRY_ID, - kind: new_entry_kind, - path: entry.path.join("\0").into(), - inode: 0, - mtime: entry.mtime, - size: entry.size, - is_ignored: entry.is_ignored, - is_external: false, - is_private: false, - is_always_included: entry.is_always_included, - canonical_path: entry.canonical_path.clone(), - char_bag: entry.char_bag, - is_fifo: entry.is_fifo, + visible_worktree_entries.push(GitEntry { + entry: Entry { + id: NEW_ENTRY_ID, + kind: new_entry_kind, + path: entry.path.join("\0").into(), + inode: 0, + mtime: entry.mtime, + size: entry.size, + is_ignored: entry.is_ignored, + is_external: false, + is_private: false, + is_always_included: entry.is_always_included, + canonical_path: entry.canonical_path.clone(), + char_bag: entry.char_bag, + is_fifo: entry.is_fifo, + }, + git_status: entry.git_status, }); } let worktree_abs_path = worktree.read(cx).abs_path(); - let (depth, path) = if Some(entry) == worktree.read(cx).root_entry() { + let (depth, path) = if Some(entry.entry) == worktree.read(cx).root_entry() { let Some(path_name) = worktree_abs_path .file_name() .with_context(|| { @@ -2458,11 +2461,7 @@ impl ProjectPanel { entry_iter.advance(); } - let git_statuses = snapshot.propagate_git_statuses(&mut visible_worktree_entries); - let mut visible_worktree_entries = visible_worktree_entries - .into_iter() - .zip(git_statuses.into_iter()) - .collect::>(); + snapshot.propagate_git_statuses(&mut visible_worktree_entries); project::sort_worktree_entries(&mut visible_worktree_entries); self.visible_entries @@ -2475,7 +2474,7 @@ impl ProjectPanel { if worktree_id == *id { entries .iter() - .position(|entry| entry.0.id == project_entry_id) + .position(|entry| entry.id == project_entry_id) } else { visited_worktrees_length += entries.len(); None @@ -2654,19 +2653,19 @@ impl ProjectPanel { return visible_worktree_entries .iter() .enumerate() - .find(|(_, entry)| entry.0.id == entry_id) + .find(|(_, entry)| entry.id == entry_id) .map(|(ix, _)| (worktree_ix, ix, total_ix + ix)); } None } - fn entry_at_index(&self, index: usize) -> Option<(WorktreeId, Option, &Entry)> { + fn entry_at_index(&self, index: usize) -> Option<(WorktreeId, GitEntryRef)> { let mut offset = 0; for (worktree_id, visible_worktree_entries, _) in &self.visible_entries { if visible_worktree_entries.len() > offset + index { return visible_worktree_entries .get(index) - .map(|(entry, git_file_status)| (*worktree_id, *git_file_status, entry)); + .map(|entry| (*worktree_id, entry.to_ref())); } offset += visible_worktree_entries.len(); } @@ -2695,11 +2694,11 @@ impl ProjectPanel { let entries = entries_paths.get_or_init(|| { visible_worktree_entries .iter() - .map(|e| (e.0.path.clone())) + .map(|e| (e.path.clone())) .collect() }); for entry in visible_worktree_entries[entry_range].iter() { - callback(&entry.0, entries, cx); + callback(&entry, entries, cx); } ix = end_ix; } @@ -2744,11 +2743,11 @@ impl ProjectPanel { let entries = entries_paths.get_or_init(|| { visible_worktree_entries .iter() - .map(|e| (e.0.path.clone())) + .map(|e| (e.path.clone())) .collect() }); - for (entry, git_status) in visible_worktree_entries[entry_range].iter() { - let status = git_status_setting.then_some(*git_status).flatten(); + for entry in visible_worktree_entries[entry_range].iter() { + let status = git_status_setting.then_some(entry.git_status).flatten(); let is_expanded = expanded_entry_ids.binary_search(&entry.id).is_ok(); let icon = match entry.kind { EntryKind::File => { @@ -2897,9 +2896,9 @@ impl ProjectPanel { worktree_id: WorktreeId, reverse_search: bool, only_visible_entries: bool, - predicate: impl Fn(&Entry, Option, WorktreeId) -> bool, + predicate: impl Fn(GitEntryRef, WorktreeId) -> bool, cx: &mut ViewContext, - ) -> Option { + ) -> Option { if only_visible_entries { let entries = self .visible_entries @@ -2914,19 +2913,18 @@ impl ProjectPanel { .clone(); return utils::ReversibleIterable::new(entries.iter(), reverse_search) - .find(|ele| predicate(&ele.0, ele.1, worktree_id)) - .map(|ele| ele.0); + .find(|ele| predicate(ele.to_ref(), worktree_id)) + .cloned(); } let worktree = self.project.read(cx).worktree_for_id(worktree_id, cx)?; worktree.update(cx, |tree, _| { utils::ReversibleIterable::new( - tree.entries(true, 0usize) - .with_git_statuses(&tree.snapshot()), + tree.entries(true, 0usize).with_git_statuses(), reverse_search, ) - .find_single_ended(|ele| predicate(&ele.0, ele.1, worktree_id)) - .map(|ele| ele.0) + .find_single_ended(|ele| predicate(*ele, worktree_id)) + .map(|ele| ele.to_owned()) }) } @@ -2934,7 +2932,7 @@ impl ProjectPanel { &self, start: Option<&SelectedEntry>, reverse_search: bool, - predicate: impl Fn(&Entry, Option, WorktreeId) -> bool, + predicate: impl Fn(GitEntryRef, WorktreeId) -> bool, cx: &mut ViewContext, ) -> Option { let mut worktree_ids: Vec<_> = self @@ -2956,11 +2954,9 @@ impl ProjectPanel { let root_entry = tree.root_entry()?; let tree_id = tree.id(); - // TOOD: Expose the all statuses cursor, as a wrapper over a Traversal - // The co-iterates the GitEntries as the file entries come through let mut first_iter = tree .traverse_from_path(true, true, true, entry.path.as_ref()) - .with_git_statuses(tree); + .with_git_statuses(); if reverse_search { first_iter.next(); @@ -2968,25 +2964,25 @@ impl ProjectPanel { let first = first_iter .enumerate() - .take_until(|(count, ele)| ele.0 == root_entry && *count != 0usize) - .map(|(_, ele)| ele) - .find(|ele| predicate(ele.0, ele.1 tree_id)) - .cloned(); + .take_until(|(count, entry)| entry.entry == root_entry && *count != 0usize) + .map(|(_, entry)| entry) + .find(|ele| predicate(*ele, tree_id)) + .map(|ele| ele.to_owned()); - let second_iter = tree.entries(true, 0usize); + let second_iter = tree.entries(true, 0usize).with_git_statuses(); let second = if reverse_search { second_iter .take_until(|ele| ele.id == start.entry_id) - .filter(|ele| predicate(ele, tree_id)) + .filter(|ele| predicate(*ele, tree_id)) .last() - .cloned() + .map(|ele| ele.to_owned()) } else { second_iter .take_while(|ele| ele.id != start.entry_id) - .filter(|ele| predicate(ele, tree_id)) + .filter(|ele| predicate(*ele, tree_id)) .last() - .cloned() + .map(|ele| ele.to_owned()) }; if reverse_search { @@ -3043,7 +3039,7 @@ impl ProjectPanel { &self, start: Option<&SelectedEntry>, reverse_search: bool, - predicate: impl Fn(&Entry, WorktreeId) -> bool, + predicate: impl Fn(GitEntryRef, WorktreeId) -> bool, cx: &mut ViewContext, ) -> Option { let mut worktree_ids: Vec<_> = self @@ -3085,8 +3081,8 @@ impl ProjectPanel { ) }; - let first_search = first_iter.find(|ele| predicate(ele, start.worktree_id)); - let second_search = second_iter.find(|ele| predicate(ele, start.worktree_id)); + let first_search = first_iter.find(|ele| predicate(ele.to_ref(), start.worktree_id)); + let second_search = second_iter.find(|ele| predicate(ele.to_ref(), start.worktree_id)); if first_search.is_some() { return first_search.map(|entry| SelectedEntry { @@ -4026,8 +4022,7 @@ impl Render for ProjectPanel { if cx.modifiers().secondary() { let ix = active_indent_guide.offset.y; let Some((target_entry, worktree)) = maybe!({ - let (worktree_id, _git_status, entry) = - this.entry_at_index(ix)?; + let (worktree_id, entry) = this.entry_at_index(ix)?; let worktree = this .project .read(cx) diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index a92e95aedc..93440ae531 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -193,7 +193,7 @@ pub struct RepositoryEntry { /// With this setup, this field would contain 2 entries, like so: /// - my_sub_folder_1/project_root/changed_file_1 /// - my_sub_folder_2/changed_file_2 - pub(crate) git_entries_by_path: SumTree, + pub(crate) git_entries_by_path: SumTree, pub(crate) work_directory_id: ProjectEntryId, pub(crate) work_directory: WorkDirectory, pub(crate) branch: Option>, @@ -226,7 +226,7 @@ impl RepositoryEntry { self.into() } - pub fn status(&self) -> impl Iterator + '_ { + pub fn status(&self) -> impl Iterator + '_ { self.git_entries_by_path.iter().cloned() } } @@ -2480,7 +2480,7 @@ impl Snapshot { } #[cfg(any(feature = "test-support", test))] - pub fn git_satus(&self, work_dir: &Path) -> Option> { + pub fn git_satus(&self, work_dir: &Path) -> Option> { self.repositories .get(&PathKey(work_dir.into()), &()) .map(|repo| repo.status().collect()) @@ -2532,7 +2532,7 @@ impl Snapshot { }) } - pub fn propagate_git_statuses(&self, entries: &[Entry]) -> Vec> { + pub fn propagate_git_statuses(&self, entries: &mut [GitEntry]) -> Vec> { let mut cursor = all_statuses_cursor(self); let mut entry_stack = Vec::<(usize, GitStatuses)>::new(); @@ -3586,7 +3586,7 @@ pub type UpdatedEntriesSet = Arc<[(Arc, ProjectEntryId, PathChange)]>; pub type UpdatedGitRepositoriesSet = Arc<[(Arc, GitRepositoryChange)]>; #[derive(Clone, Debug, PartialEq, Eq)] -pub struct GitEntry { +pub struct StatusEntry { pub path: RepoPath, pub git_status: GitFileStatus, } @@ -3665,7 +3665,7 @@ impl sum_tree::Summary for GitStatuses { } } -impl sum_tree::Item for GitEntry { +impl sum_tree::Item for StatusEntry { type Summary = PathSummary; fn summary(&self, _: &::Context) -> Self::Summary { @@ -3694,7 +3694,7 @@ impl sum_tree::Item for GitEntry { } } -impl sum_tree::KeyedItem for GitEntry { +impl sum_tree::KeyedItem for StatusEntry { type Key = PathKey; fn key(&self) -> Self::Key { @@ -3779,7 +3779,7 @@ struct AllStatusesCursor<'a, I> { repos: I, current_location: Option<( &'a WorkDirectory, - Cursor<'a, GitEntry, (TraversalProgress<'a>, GitStatuses)>, + Cursor<'a, StatusEntry, (TraversalProgress<'a>, GitStatuses)>, )>, statuses_before_current_repo: GitStatuses, } @@ -4809,7 +4809,7 @@ impl BackgroundScanner { let mut changed_path_statuses = Vec::new(); for (repo_path, status) in &*status.entries { paths.remove_repo_path(repo_path); - changed_path_statuses.push(Edit::Insert(GitEntry { + changed_path_statuses.push(Edit::Insert(StatusEntry { path: repo_path.clone(), git_status: *status, })); @@ -5226,7 +5226,7 @@ impl BackgroundScanner { }; new_entries_by_path.insert_or_replace( - GitEntry { + StatusEntry { path: path.clone(), git_status: *status, }, @@ -5639,12 +5639,13 @@ impl<'a> Default for TraversalProgress<'a> { } } -pub struct EntryWithGitStatusRef<'a> { +#[derive(Debug, Clone, Copy)] +pub struct GitEntryRef<'a> { pub entry: &'a Entry, pub git_status: Option, } -impl<'a> EntryWithGitStatusRef<'a> { +impl<'a> GitEntryRef<'a> { fn entry(entry: &'a Entry) -> Self { Self { entry, @@ -5652,15 +5653,15 @@ impl<'a> EntryWithGitStatusRef<'a> { } } - pub fn to_owned(&self) -> EntryWithGitStatus { - EntryWithGitStatus { + pub fn to_owned(&self) -> GitEntry { + GitEntry { entry: self.entry.clone(), git_status: self.git_status.clone(), } } } -impl<'a> Deref for EntryWithGitStatusRef<'a> { +impl<'a> Deref for GitEntryRef<'a> { type Target = Entry; fn deref(&self) -> &Self::Target { @@ -5668,12 +5669,28 @@ impl<'a> Deref for EntryWithGitStatusRef<'a> { } } -pub struct EntryWithGitStatus { +impl<'a> AsRef for GitEntryRef<'a> { + fn as_ref(&self) -> &Entry { + self.entry + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GitEntry { pub entry: Entry, pub git_status: Option, } -impl Deref for EntryWithGitStatus { +impl GitEntry { + pub fn to_ref(&self) -> GitEntryRef { + GitEntryRef { + entry: &self.entry, + git_status: self.git_status.clone(), + } + } +} + +impl Deref for GitEntry { type Target = Entry; fn deref(&self) -> &Self::Target { @@ -5681,11 +5698,17 @@ impl Deref for EntryWithGitStatus { } } +impl<'a> AsRef for GitEntry { + fn as_ref(&self) -> &Entry { + &self.entry + } +} + pub struct GitTraversal<'a> { traversal: Traversal<'a>, reset: bool, repositories: Cursor<'a, RepositoryEntry, PathProgress<'a>>, - statuses: Option>>, + statuses: Option>>, } impl<'a> GitTraversal<'a> { @@ -5718,7 +5741,7 @@ impl<'a> GitTraversal<'a> { self.traversal.end_offset() } - pub fn entry(&mut self) -> Option> { + pub fn entry(&mut self) -> Option> { let reset = mem::take(&mut self.reset); let entry = self.traversal.cursor.item()?; let current_repository_id = self @@ -5735,7 +5758,7 @@ impl<'a> GitTraversal<'a> { } let Some(repository) = self.repositories.item() else { self.statuses = None; - return Some(EntryWithGitStatusRef::entry(entry)); + return Some(GitEntryRef::entry(entry)); }; if reset || Some(repository.work_directory_id) != current_repository_id { @@ -5743,30 +5766,30 @@ impl<'a> GitTraversal<'a> { } let Some(statuses) = self.statuses.as_mut() else { - return Some(EntryWithGitStatusRef::entry(entry)); + return Some(GitEntryRef::entry(entry)); }; let Some(repo_path) = repository.relativize(&entry.path).ok() else { - return Some(EntryWithGitStatusRef::entry(entry)); + return Some(GitEntryRef::entry(entry)); }; let found = statuses.seek_forward(&PathTarget::Path(&repo_path.0), Bias::Left, &()); if found { let Some(status) = statuses.item() else { - return Some(EntryWithGitStatusRef::entry(entry)); + return Some(GitEntryRef::entry(entry)); }; - Some(EntryWithGitStatusRef { + Some(GitEntryRef { entry, git_status: Some(status.git_status), }) } else { - Some(EntryWithGitStatusRef::entry(entry)) + Some(GitEntryRef::entry(entry)) } } } impl<'a> Iterator for GitTraversal<'a> { - type Item = EntryWithGitStatusRef<'a>; + type Item = GitEntryRef<'a>; fn next(&mut self) -> Option { if let Some(item) = self.entry() { self.advance(); @@ -6009,6 +6032,20 @@ pub struct ChildEntriesIter<'a> { traversal: Traversal<'a>, } +impl<'a> ChildEntriesIter<'a> { + pub fn with_git_statuses(self) -> ChildEntriesGitIter<'a> { + ChildEntriesGitIter { + parent_path: self.parent_path, + traversal: self.traversal.with_git_statuses(), + } + } +} + +pub struct ChildEntriesGitIter<'a> { + parent_path: &'a Path, + traversal: GitTraversal<'a>, +} + impl<'a> Iterator for ChildEntriesIter<'a> { type Item = &'a Entry; @@ -6023,6 +6060,20 @@ impl<'a> Iterator for ChildEntriesIter<'a> { } } +impl<'a> Iterator for ChildEntriesGitIter<'a> { + type Item = GitEntryRef<'a>; + + fn next(&mut self) -> Option { + if let Some(item) = self.traversal.entry() { + if item.path.starts_with(self.parent_path) { + self.traversal.advance_to_sibling(); + return Some(item); + } + } + None + } +} + impl<'a> From<&'a Entry> for proto::Entry { fn from(entry: &'a Entry) -> Self { Self { diff --git a/crates/worktree/src/worktree_tests.rs b/crates/worktree/src/worktree_tests.rs index 94e3e7458c..80772139d4 100644 --- a/crates/worktree/src/worktree_tests.rs +++ b/crates/worktree/src/worktree_tests.rs @@ -1,6 +1,6 @@ use crate::{ - worktree_settings::WorktreeSettings, Entry, EntryKind, Event, PathChange, Snapshot, Worktree, - WorktreeModelHandle, + worktree_settings::WorktreeSettings, Entry, EntryKind, Event, GitEntry, PathChange, Snapshot, + Worktree, WorktreeModelHandle, }; use anyhow::Result; use fs::{FakeFs, Fs, RealFs, RemoveOptions}; @@ -3183,7 +3183,10 @@ fn check_propagated_statuses( .iter() .map(|(path, _)| snapshot.entry_for_path(path).unwrap().clone()) .collect::>(); - let statuses = snapshot.propagate_git_statuses(&entries); + // TODO: recreate this + // let statuses = snapshot.propagate_git_statuses(&entries); + let statuses: Vec> = Vec::new(); + panic!("Redo git status propogation"); assert_eq!( entries .iter()