Fix some syncing issues with git statuses (#25535)

Like the real app, this one infinite loops if you have a diff in an
UnsharedFile.

Release Notes:

- N/A *or* Added/Fixed/Improved ...

---------

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
Conrad Irwin 2025-02-25 22:09:02 -07:00 committed by GitHub
parent 88baf171c3
commit 08539b32d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 283 additions and 6 deletions

View file

@ -5,12 +5,20 @@ mod mac_watcher;
pub mod fs_watcher;
use anyhow::{anyhow, Context as _, Result};
#[cfg(any(test, feature = "test-support"))]
use collections::HashMap;
#[cfg(any(test, feature = "test-support"))]
use git::status::StatusCode;
#[cfg(any(test, feature = "test-support"))]
use git::status::TrackedStatus;
use git::GitHostingProviderRegistry;
#[cfg(any(test, feature = "test-support"))]
use git::{repository::RepoPath, status::FileStatus};
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
use ashpd::desktop::trash;
#[cfg(any(test, feature = "test-support"))]
use std::collections::HashSet;
#[cfg(unix)]
use std::os::fd::AsFd;
#[cfg(unix)]
@ -1292,6 +1300,105 @@ impl FakeFs {
});
}
pub fn set_git_content_for_repo(
&self,
dot_git: &Path,
head_state: &[(RepoPath, String, Option<String>)],
) {
self.with_git_state(dot_git, true, |state| {
state.head_contents.clear();
state.head_contents.extend(
head_state
.iter()
.map(|(path, head_content, _)| (path.clone(), head_content.clone())),
);
state.index_contents.clear();
state.index_contents.extend(head_state.iter().map(
|(path, head_content, index_content)| {
(
path.clone(),
index_content.as_ref().unwrap_or(head_content).clone(),
)
},
));
});
self.recalculate_git_status(dot_git);
}
pub fn recalculate_git_status(&self, dot_git: &Path) {
let git_files: HashMap<_, _> = self
.files()
.iter()
.filter_map(|path| {
let repo_path =
RepoPath::new(path.strip_prefix(dot_git.parent().unwrap()).ok()?.into());
let content = self
.read_file_sync(path)
.ok()
.map(|content| String::from_utf8(content).unwrap());
Some((repo_path, content?))
})
.collect();
self.with_git_state(dot_git, false, |state| {
state.statuses.clear();
let mut paths: HashSet<_> = state.head_contents.keys().collect();
paths.extend(state.index_contents.keys());
paths.extend(git_files.keys());
for path in paths {
let head = state.head_contents.get(path);
let index = state.index_contents.get(path);
let fs = git_files.get(path);
let status = match (head, index, fs) {
(Some(head), Some(index), Some(fs)) => FileStatus::Tracked(TrackedStatus {
index_status: if head == index {
StatusCode::Unmodified
} else {
StatusCode::Modified
},
worktree_status: if fs == index {
StatusCode::Unmodified
} else {
StatusCode::Modified
},
}),
(Some(head), Some(index), None) => FileStatus::Tracked(TrackedStatus {
index_status: if head == index {
StatusCode::Unmodified
} else {
StatusCode::Modified
},
worktree_status: StatusCode::Deleted,
}),
(Some(_), None, Some(_)) => FileStatus::Tracked(TrackedStatus {
index_status: StatusCode::Deleted,
worktree_status: StatusCode::Added,
}),
(Some(_), None, None) => FileStatus::Tracked(TrackedStatus {
index_status: StatusCode::Deleted,
worktree_status: StatusCode::Deleted,
}),
(None, Some(index), Some(fs)) => FileStatus::Tracked(TrackedStatus {
index_status: StatusCode::Added,
worktree_status: if fs == index {
StatusCode::Unmodified
} else {
StatusCode::Modified
},
}),
(None, Some(_), None) => FileStatus::Tracked(TrackedStatus {
index_status: StatusCode::Added,
worktree_status: StatusCode::Deleted,
}),
(None, None, Some(_)) => FileStatus::Untracked,
(None, None, None) => {
unreachable!();
}
};
state.statuses.insert(path.clone(), status);
}
});
}
pub fn set_blame_for_repo(&self, dot_git: &Path, blames: Vec<(RepoPath, git::blame::Blame)>) {
self.with_git_state(dot_git, true, |state| {
state.blames.clear();