git: Migrate some panel code away from visible_entries (#23251)

To prepare for the introduction of folding in the git panel, these
codepaths need to work with the canonical source of all git status
entries, not just the ones that are visible in the panel.

Release Notes:

- N/A

---------

Co-authored-by: Nate <nate@zed.dev>
This commit is contained in:
Cole Miller 2025-01-16 16:32:11 -05:00 committed by GitHub
parent 1b1c2e55f3
commit 1a8303b020
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 190 additions and 162 deletions

View file

@ -171,7 +171,13 @@ impl FileStatus {
FileStatus::Tracked(TrackedStatus {
index_status,
worktree_status,
}) => index_status.summary() + worktree_status.summary(),
}) => {
let mut summary = index_status.to_summary() + worktree_status.to_summary();
if summary != GitSummary::UNCHANGED {
summary.count = 1;
};
summary
}
}
}
}
@ -190,11 +196,23 @@ impl StatusCode {
}
}
fn summary(self) -> GitSummary {
/// Returns the contribution of this status code to the Git summary.
///
/// Note that this does not include the count field, which must be set manually.
fn to_summary(self) -> GitSummary {
match self {
StatusCode::Modified | StatusCode::TypeChanged => GitSummary::MODIFIED,
StatusCode::Added => GitSummary::ADDED,
StatusCode::Deleted => GitSummary::DELETED,
StatusCode::Modified | StatusCode::TypeChanged => GitSummary {
modified: 1,
..GitSummary::UNCHANGED
},
StatusCode::Added => GitSummary {
added: 1,
..GitSummary::UNCHANGED
},
StatusCode::Deleted => GitSummary {
deleted: 1,
..GitSummary::UNCHANGED
},
StatusCode::Renamed | StatusCode::Copied | StatusCode::Unmodified => {
GitSummary::UNCHANGED
}
@ -220,31 +238,19 @@ pub struct GitSummary {
pub conflict: usize,
pub untracked: usize,
pub deleted: usize,
pub count: usize,
}
impl GitSummary {
pub const ADDED: Self = Self {
added: 1,
..Self::UNCHANGED
};
pub const MODIFIED: Self = Self {
modified: 1,
..Self::UNCHANGED
};
pub const CONFLICT: Self = Self {
conflict: 1,
..Self::UNCHANGED
};
pub const DELETED: Self = Self {
deleted: 1,
count: 1,
..Self::UNCHANGED
};
pub const UNTRACKED: Self = Self {
untracked: 1,
count: 1,
..Self::UNCHANGED
};
@ -254,6 +260,7 @@ impl GitSummary {
conflict: 0,
untracked: 0,
deleted: 0,
count: 0,
};
}
@ -291,6 +298,7 @@ impl std::ops::AddAssign for GitSummary {
self.conflict += rhs.conflict;
self.untracked += rhs.untracked;
self.deleted += rhs.deleted;
self.count += rhs.count;
}
}
@ -304,6 +312,7 @@ impl std::ops::Sub for GitSummary {
conflict: self.conflict - rhs.conflict,
untracked: self.untracked - rhs.untracked,
deleted: self.deleted - rhs.deleted,
count: self.count - rhs.count,
}
}
}

View file

@ -88,10 +88,6 @@ pub struct GitPanel {
show_scrollbar: bool,
rebuild_requested: Arc<AtomicBool>,
commit_editor: View<Editor>,
/// The visible entries in the list, accounting for folding & expanded state.
///
/// At this point it doesn't matter what repository the entry belongs to,
/// as only one repositories' entries are visible in the list at a time.
visible_entries: Vec<GitListEntry>,
all_staged: Option<bool>,
width: Option<Pixels>,
@ -363,8 +359,8 @@ impl GitPanel {
git_panel
}
fn git_state<'a>(&self, cx: &'a AppContext) -> Option<&'a Model<GitState>> {
self.project.read(cx).git_state()
fn git_state(&self, cx: &AppContext) -> Option<Model<GitState>> {
self.project.read(cx).git_state().cloned()
}
fn active_repository<'a>(
@ -578,7 +574,7 @@ impl GitPanel {
}
fn select_first_entry_if_none(&mut self, cx: &mut ViewContext<Self>) {
if !self.no_entries() && self.selected_entry.is_none() {
if !self.no_entries(cx) && self.selected_entry.is_none() {
self.selected_entry = Some(0);
self.scroll_to_selected_entry(cx);
cx.notify();
@ -597,15 +593,24 @@ impl GitPanel {
.and_then(|i| self.visible_entries.get(i))
}
fn open_selected(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
if let Some(entry) = self
.selected_entry
.and_then(|i| self.visible_entries.get(i))
{
self.open_entry(entry, cx);
}
}
fn toggle_staged_for_entry(&mut self, entry: &GitListEntry, cx: &mut ViewContext<Self>) {
let Some(git_state) = self.git_state(cx).cloned() else {
let Some(git_state) = self.git_state(cx) else {
return;
};
git_state.update(cx, |git_state, _| {
if entry.status.is_staged().unwrap_or(false) {
git_state.unstage_entry(entry.repo_path.clone());
git_state.stage_entries(vec![entry.repo_path.clone()]);
} else {
git_state.stage_entry(entry.repo_path.clone());
git_state.stage_entries(vec![entry.repo_path.clone()]);
}
});
cx.notify();
@ -617,15 +622,6 @@ impl GitPanel {
}
}
fn open_selected(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
if let Some(entry) = self
.selected_entry
.and_then(|i| self.visible_entries.get(i))
{
self.open_entry(entry, cx);
}
}
fn open_entry(&self, entry: &GitListEntry, cx: &mut ViewContext<Self>) {
let Some((worktree_id, path)) = maybe!({
let git_state = self.git_state(cx)?;
@ -646,32 +642,25 @@ impl GitPanel {
}
fn stage_all(&mut self, _: &git::StageAll, cx: &mut ViewContext<Self>) {
let to_stage = self
.visible_entries
.iter_mut()
.filter_map(|entry| {
let is_unstaged = !entry.is_staged.unwrap_or(false);
entry.is_staged = Some(true);
is_unstaged.then(|| entry.repo_path.clone())
})
.collect();
self.all_staged = Some(true);
let Some(git_state) = self.git_state(cx).cloned() else {
let Some(git_state) = self.git_state(cx) else {
return;
};
git_state.update(cx, |git_state, _| git_state.stage_entries(to_stage));
for entry in &mut self.visible_entries {
entry.is_staged = Some(true);
}
self.all_staged = Some(true);
git_state.read(cx).stage_all();
}
fn unstage_all(&mut self, _: &git::UnstageAll, cx: &mut ViewContext<Self>) {
// This should only be called when all entries are staged.
let Some(git_state) = self.git_state(cx) else {
return;
};
for entry in &mut self.visible_entries {
entry.is_staged = Some(false);
}
self.all_staged = Some(false);
let Some(git_state) = self.git_state(cx).cloned() else {
return;
};
git_state.update(cx, |git_state, _| git_state.unstage_all());
git_state.read(cx).unstage_all();
}
fn discard_all(&mut self, _: &git::RevertAll, _cx: &mut ViewContext<Self>) {
@ -680,7 +669,7 @@ impl GitPanel {
}
fn clear_message(&mut self, cx: &mut ViewContext<Self>) {
let Some(git_state) = self.git_state(cx).cloned() else {
let Some(git_state) = self.git_state(cx) else {
return;
};
git_state.update(cx, |git_state, _| {
@ -690,10 +679,28 @@ impl GitPanel {
.update(cx, |editor, cx| editor.set_text("", cx));
}
fn can_commit(&self, commit_all: bool, cx: &AppContext) -> bool {
let Some(git_state) = self.git_state(cx) else {
return false;
};
let has_message = !self.commit_editor.read(cx).text(cx).is_empty();
let has_changes = git_state.read(cx).entry_count() > 0;
let has_staged_changes = self
.visible_entries
.iter()
.any(|entry| entry.is_staged == Some(true));
has_message && (commit_all || has_staged_changes) && has_changes
}
/// Commit all staged changes
fn commit_changes(&mut self, _: &git::CommitChanges, cx: &mut ViewContext<Self>) {
self.clear_message(cx);
if !self.can_commit(false, cx) {
return;
}
// TODO: Implement commit all staged
println!("Commit staged changes triggered");
}
@ -702,16 +709,17 @@ impl GitPanel {
fn commit_all_changes(&mut self, _: &git::CommitAllChanges, cx: &mut ViewContext<Self>) {
self.clear_message(cx);
if !self.can_commit(true, cx) {
return;
}
// TODO: Implement commit all changes
println!("Commit all changes triggered");
}
fn no_entries(&self) -> bool {
self.visible_entries.is_empty()
}
fn entry_count(&self) -> usize {
self.visible_entries.len()
fn no_entries(&self, cx: &mut ViewContext<Self>) -> bool {
self.git_state(cx)
.map_or(true, |git_state| git_state.read(cx).entry_count() == 0)
}
fn for_each_visible_entry(
@ -828,7 +836,7 @@ impl GitPanel {
if let language::BufferEvent::Reparsed | language::BufferEvent::Edited = event {
let commit_message = self.commit_editor.update(cx, |editor, cx| editor.text(cx));
let Some(git_state) = self.git_state(cx).cloned() else {
let Some(git_state) = self.git_state(cx) else {
return;
};
git_state.update(cx, |git_state, _| {
@ -866,8 +874,11 @@ impl GitPanel {
pub fn render_panel_header(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let focus_handle = self.focus_handle(cx).clone();
let entry_count = self
.git_state(cx)
.map_or(0, |git_state| git_state.read(cx).entry_count());
let changes_string = match self.entry_count() {
let changes_string = match entry_count {
0 => "No changes".to_string(),
1 => "1 change".to_string(),
n => format!("{} changes", n),
@ -887,7 +898,7 @@ impl GitPanel {
.child(
Checkbox::new(
"all-changes",
if self.no_entries() {
if self.no_entries(cx) {
ToggleState::Selected
} else {
self.all_staged
@ -1108,7 +1119,8 @@ impl GitPanel {
}
fn render_entries(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let entry_count = self.entry_count();
let entry_count = self.visible_entries.len();
h_flex()
.size_full()
.overflow_hidden()
@ -1228,14 +1240,16 @@ impl GitPanel {
ToggleState::Indeterminate => None,
};
let repo_path = repo_path.clone();
let Some(git_state) = this.git_state(cx).cloned() else {
let Some(git_state) = this.git_state(cx) else {
return;
};
git_state.update(cx, |git_state, _| match toggle {
ToggleState::Selected | ToggleState::Indeterminate => {
git_state.stage_entry(repo_path);
git_state.stage_entries(vec![repo_path]);
}
ToggleState::Unselected => {
git_state.unstage_entries(vec![repo_path])
}
ToggleState::Unselected => git_state.unstage_entry(repo_path),
})
});
}
@ -1330,7 +1344,7 @@ impl Render for GitPanel {
.bg(ElevationIndex::Surface.bg(cx))
.child(self.render_panel_header(cx))
.child(self.render_divider(cx))
.child(if !self.no_entries() {
.child(if !self.no_entries(cx) {
self.render_entries(cx).into_any_element()
} else {
self.render_empty_state(cx).into_any_element()

View file

@ -73,52 +73,54 @@ impl GitState {
self.commit_message = None;
}
pub fn stage_entry(&mut self, repo_path: RepoPath) {
fn act_on_entries(&self, entries: Vec<RepoPath>, action: StatusAction) {
if entries.is_empty() {
return;
}
if let Some((_, _, git_repo)) = self.active_repository.as_ref() {
let _ = self.update_sender.unbounded_send((
git_repo.clone(),
vec![repo_path],
StatusAction::Stage,
));
let _ = self
.update_sender
.unbounded_send((git_repo.clone(), entries, action));
}
}
pub fn unstage_entry(&mut self, repo_path: RepoPath) {
if let Some((_, _, git_repo)) = self.active_repository.as_ref() {
let _ = self.update_sender.unbounded_send((
git_repo.clone(),
vec![repo_path],
StatusAction::Unstage,
));
}
pub fn stage_entries(&self, entries: Vec<RepoPath>) {
self.act_on_entries(entries, StatusAction::Stage);
}
pub fn stage_entries(&mut self, entries: Vec<RepoPath>) {
if let Some((_, _, git_repo)) = self.active_repository.as_ref() {
let _ =
self.update_sender
.unbounded_send((git_repo.clone(), entries, StatusAction::Stage));
}
pub fn unstage_entries(&self, entries: Vec<RepoPath>) {
self.act_on_entries(entries, StatusAction::Unstage);
}
fn act_on_all(&mut self, action: StatusAction) {
if let Some((_, active_repository, git_repo)) = self.active_repository.as_ref() {
let _ = self.update_sender.unbounded_send((
git_repo.clone(),
active_repository
.status()
.map(|entry| entry.repo_path)
.collect(),
action,
));
}
pub fn stage_all(&self) {
let Some((_, entry, _)) = self.active_repository.as_ref() else {
return;
};
let to_stage = entry
.status()
.filter(|entry| !entry.status.is_staged().unwrap_or(false))
.map(|entry| entry.repo_path.clone())
.collect();
self.stage_entries(to_stage);
}
pub fn stage_all(&mut self) {
self.act_on_all(StatusAction::Stage);
pub fn unstage_all(&self) {
let Some((_, entry, _)) = self.active_repository.as_ref() else {
return;
};
let to_unstage = entry
.status()
.filter(|entry| entry.status.is_staged().unwrap_or(true))
.map(|entry| entry.repo_path.clone())
.collect();
self.unstage_entries(to_unstage);
}
pub fn unstage_all(&mut self) {
self.act_on_all(StatusAction::Unstage);
/// Get a count of all entries in the active repository, including
/// untracked files.
pub fn entry_count(&self) -> usize {
self.active_repository
.as_ref()
.map_or(0, |(_, entry, _)| entry.status_len())
}
}

View file

@ -227,6 +227,10 @@ impl RepositoryEntry {
self.statuses_by_path.iter().cloned()
}
pub fn status_len(&self) -> usize {
self.statuses_by_path.summary().item_summary.count
}
pub fn status_for_path(&self, path: &RepoPath) -> Option<StatusEntry> {
self.statuses_by_path
.get(&PathKey(path.0.clone()), &())
@ -5718,7 +5722,7 @@ impl<'a> GitTraversal<'a> {
if statuses.seek_forward(&PathTarget::Path(repo_path.as_ref()), Bias::Left, &()) {
self.current_entry_summary = Some(statuses.item().unwrap().status.into());
} else {
self.current_entry_summary = Some(GitSummary::zero(&()));
self.current_entry_summary = Some(GitSummary::UNCHANGED);
}
}
}
@ -5755,7 +5759,7 @@ impl<'a> GitTraversal<'a> {
pub fn entry(&self) -> Option<GitEntryRef<'a>> {
let entry = self.traversal.cursor.item()?;
let git_summary = self.current_entry_summary.unwrap_or_default();
let git_summary = self.current_entry_summary.unwrap_or(GitSummary::UNCHANGED);
Some(GitEntryRef { entry, git_summary })
}
}

View file

@ -1512,9 +1512,9 @@ async fn test_bump_mtime_of_git_repo_workdir(cx: &mut TestAppContext) {
check_git_statuses(
&snapshot,
&[
(Path::new(""), GitSummary::MODIFIED),
(Path::new(""), MODIFIED),
(Path::new("a.txt"), GitSummary::UNCHANGED),
(Path::new("b/c.txt"), GitSummary::MODIFIED),
(Path::new("b/c.txt"), MODIFIED),
],
);
}
@ -2811,7 +2811,7 @@ async fn test_traverse_with_git_status(cx: &mut TestAppContext) {
assert_eq!(entry.git_summary, GitSummary::UNCHANGED);
let entry = traversal.next().unwrap();
assert_eq!(entry.path.as_ref(), Path::new("x/x2.txt"));
assert_eq!(entry.git_summary, GitSummary::MODIFIED);
assert_eq!(entry.git_summary, MODIFIED);
let entry = traversal.next().unwrap();
assert_eq!(entry.path.as_ref(), Path::new("x/y/y1.txt"));
assert_eq!(entry.git_summary, GitSummary::CONFLICT);
@ -2820,13 +2820,13 @@ async fn test_traverse_with_git_status(cx: &mut TestAppContext) {
assert_eq!(entry.git_summary, GitSummary::UNCHANGED);
let entry = traversal.next().unwrap();
assert_eq!(entry.path.as_ref(), Path::new("x/z.txt"));
assert_eq!(entry.git_summary, GitSummary::ADDED);
assert_eq!(entry.git_summary, ADDED);
let entry = traversal.next().unwrap();
assert_eq!(entry.path.as_ref(), Path::new("z/z1.txt"));
assert_eq!(entry.git_summary, GitSummary::UNCHANGED);
let entry = traversal.next().unwrap();
assert_eq!(entry.path.as_ref(), Path::new("z/z2.txt"));
assert_eq!(entry.git_summary, GitSummary::ADDED);
assert_eq!(entry.git_summary, ADDED);
}
#[gpui::test]
@ -2893,10 +2893,7 @@ async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
check_git_statuses(
&snapshot,
&[
(
Path::new(""),
GitSummary::CONFLICT + GitSummary::MODIFIED + GitSummary::ADDED,
),
(Path::new(""), GitSummary::CONFLICT + MODIFIED + ADDED),
(Path::new("g"), GitSummary::CONFLICT),
(Path::new("g/h2.txt"), GitSummary::CONFLICT),
],
@ -2905,16 +2902,13 @@ async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
check_git_statuses(
&snapshot,
&[
(
Path::new(""),
GitSummary::CONFLICT + GitSummary::ADDED + GitSummary::MODIFIED,
),
(Path::new("a"), GitSummary::ADDED + GitSummary::MODIFIED),
(Path::new("a/b"), GitSummary::ADDED),
(Path::new("a/b/c1.txt"), GitSummary::ADDED),
(Path::new(""), GitSummary::CONFLICT + ADDED + MODIFIED),
(Path::new("a"), ADDED + MODIFIED),
(Path::new("a/b"), ADDED),
(Path::new("a/b/c1.txt"), ADDED),
(Path::new("a/b/c2.txt"), GitSummary::UNCHANGED),
(Path::new("a/d"), GitSummary::MODIFIED),
(Path::new("a/d/e2.txt"), GitSummary::MODIFIED),
(Path::new("a/d"), MODIFIED),
(Path::new("a/d/e2.txt"), MODIFIED),
(Path::new("f"), GitSummary::UNCHANGED),
(Path::new("f/no-status.txt"), GitSummary::UNCHANGED),
(Path::new("g"), GitSummary::CONFLICT),
@ -2925,12 +2919,12 @@ async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
check_git_statuses(
&snapshot,
&[
(Path::new("a/b"), GitSummary::ADDED),
(Path::new("a/b/c1.txt"), GitSummary::ADDED),
(Path::new("a/b"), ADDED),
(Path::new("a/b/c1.txt"), ADDED),
(Path::new("a/b/c2.txt"), GitSummary::UNCHANGED),
(Path::new("a/d"), GitSummary::MODIFIED),
(Path::new("a/d"), MODIFIED),
(Path::new("a/d/e1.txt"), GitSummary::UNCHANGED),
(Path::new("a/d/e2.txt"), GitSummary::MODIFIED),
(Path::new("a/d/e2.txt"), MODIFIED),
(Path::new("f"), GitSummary::UNCHANGED),
(Path::new("f/no-status.txt"), GitSummary::UNCHANGED),
(Path::new("g"), GitSummary::CONFLICT),
@ -2940,10 +2934,10 @@ async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
check_git_statuses(
&snapshot,
&[
(Path::new("a/b/c1.txt"), GitSummary::ADDED),
(Path::new("a/b/c1.txt"), ADDED),
(Path::new("a/b/c2.txt"), GitSummary::UNCHANGED),
(Path::new("a/d/e1.txt"), GitSummary::UNCHANGED),
(Path::new("a/d/e2.txt"), GitSummary::MODIFIED),
(Path::new("a/d/e2.txt"), MODIFIED),
(Path::new("f/no-status.txt"), GitSummary::UNCHANGED),
],
);
@ -3016,49 +3010,43 @@ async fn test_propagate_statuses_for_repos_under_project(cx: &mut TestAppContext
check_git_statuses(
&snapshot,
&[
(Path::new("x"), GitSummary::ADDED),
(Path::new("x/x1.txt"), GitSummary::ADDED),
],
&[(Path::new("x"), ADDED), (Path::new("x/x1.txt"), ADDED)],
);
check_git_statuses(
&snapshot,
&[
(Path::new("y"), GitSummary::CONFLICT + GitSummary::MODIFIED),
(Path::new("y"), GitSummary::CONFLICT + MODIFIED),
(Path::new("y/y1.txt"), GitSummary::CONFLICT),
(Path::new("y/y2.txt"), GitSummary::MODIFIED),
(Path::new("y/y2.txt"), MODIFIED),
],
);
check_git_statuses(
&snapshot,
&[
(Path::new("z"), GitSummary::MODIFIED),
(Path::new("z/z2.txt"), GitSummary::MODIFIED),
(Path::new("z"), MODIFIED),
(Path::new("z/z2.txt"), MODIFIED),
],
);
check_git_statuses(
&snapshot,
&[
(Path::new("x"), GitSummary::ADDED),
(Path::new("x/x1.txt"), GitSummary::ADDED),
],
&[(Path::new("x"), ADDED), (Path::new("x/x1.txt"), ADDED)],
);
check_git_statuses(
&snapshot,
&[
(Path::new("x"), GitSummary::ADDED),
(Path::new("x/x1.txt"), GitSummary::ADDED),
(Path::new("x"), ADDED),
(Path::new("x/x1.txt"), ADDED),
(Path::new("x/x2.txt"), GitSummary::UNCHANGED),
(Path::new("y"), GitSummary::CONFLICT + GitSummary::MODIFIED),
(Path::new("y"), GitSummary::CONFLICT + MODIFIED),
(Path::new("y/y1.txt"), GitSummary::CONFLICT),
(Path::new("y/y2.txt"), GitSummary::MODIFIED),
(Path::new("z"), GitSummary::MODIFIED),
(Path::new("y/y2.txt"), MODIFIED),
(Path::new("z"), MODIFIED),
(Path::new("z/z1.txt"), GitSummary::UNCHANGED),
(Path::new("z/z2.txt"), GitSummary::MODIFIED),
(Path::new("z/z2.txt"), MODIFIED),
],
);
}
@ -3139,9 +3127,9 @@ async fn test_propagate_statuses_for_nested_repos(cx: &mut TestAppContext) {
check_git_statuses(
&snapshot,
&[
(Path::new("z"), GitSummary::ADDED),
(Path::new("z"), ADDED),
(Path::new("z/z1.txt"), GitSummary::UNCHANGED),
(Path::new("z/z2.txt"), GitSummary::ADDED),
(Path::new("z/z2.txt"), ADDED),
],
);
@ -3149,7 +3137,7 @@ async fn test_propagate_statuses_for_nested_repos(cx: &mut TestAppContext) {
check_git_statuses(
&snapshot,
&[
(Path::new("x"), GitSummary::MODIFIED + GitSummary::ADDED),
(Path::new("x"), MODIFIED + ADDED),
(Path::new("x/y"), GitSummary::CONFLICT),
(Path::new("x/y/y1.txt"), GitSummary::CONFLICT),
],
@ -3159,13 +3147,13 @@ async fn test_propagate_statuses_for_nested_repos(cx: &mut TestAppContext) {
check_git_statuses(
&snapshot,
&[
(Path::new("x"), GitSummary::MODIFIED + GitSummary::ADDED),
(Path::new("x"), MODIFIED + ADDED),
(Path::new("x/x1.txt"), GitSummary::UNCHANGED),
(Path::new("x/x2.txt"), GitSummary::MODIFIED),
(Path::new("x/x2.txt"), MODIFIED),
(Path::new("x/y"), GitSummary::CONFLICT),
(Path::new("x/y/y1.txt"), GitSummary::CONFLICT),
(Path::new("x/y/y2.txt"), GitSummary::UNCHANGED),
(Path::new("x/z.txt"), GitSummary::ADDED),
(Path::new("x/z.txt"), ADDED),
],
);
@ -3174,7 +3162,7 @@ async fn test_propagate_statuses_for_nested_repos(cx: &mut TestAppContext) {
&snapshot,
&[
(Path::new(""), GitSummary::UNCHANGED),
(Path::new("x"), GitSummary::MODIFIED + GitSummary::ADDED),
(Path::new("x"), MODIFIED + ADDED),
(Path::new("x/x1.txt"), GitSummary::UNCHANGED),
],
);
@ -3184,16 +3172,16 @@ async fn test_propagate_statuses_for_nested_repos(cx: &mut TestAppContext) {
&snapshot,
&[
(Path::new(""), GitSummary::UNCHANGED),
(Path::new("x"), GitSummary::MODIFIED + GitSummary::ADDED),
(Path::new("x"), MODIFIED + ADDED),
(Path::new("x/x1.txt"), GitSummary::UNCHANGED),
(Path::new("x/x2.txt"), GitSummary::MODIFIED),
(Path::new("x/x2.txt"), MODIFIED),
(Path::new("x/y"), GitSummary::CONFLICT),
(Path::new("x/y/y1.txt"), GitSummary::CONFLICT),
(Path::new("x/y/y2.txt"), GitSummary::UNCHANGED),
(Path::new("x/z.txt"), GitSummary::ADDED),
(Path::new("z"), GitSummary::ADDED),
(Path::new("x/z.txt"), ADDED),
(Path::new("z"), ADDED),
(Path::new("z/z1.txt"), GitSummary::UNCHANGED),
(Path::new("z/z2.txt"), GitSummary::ADDED),
(Path::new("z/z2.txt"), ADDED),
],
);
}
@ -3238,6 +3226,17 @@ fn check_git_statuses(snapshot: &Snapshot, expected_statuses: &[(&Path, GitSumma
assert_eq!(found_statuses, expected_statuses);
}
const ADDED: GitSummary = GitSummary {
added: 1,
count: 1,
..GitSummary::UNCHANGED
};
const MODIFIED: GitSummary = GitSummary {
modified: 1,
count: 1,
..GitSummary::UNCHANGED
};
#[track_caller]
fn git_init(path: &Path) -> git2::Repository {
git2::Repository::init(path).expect("Failed to initialize git repository")