Merge pull request 'Add visibleParentResolver and optimize Git file status lookup' (#42) from performance into main

This commit was merged in pull request #42.
This commit is contained in:
2026-09-11 20:35:19 +00:00
5 changed files with 141 additions and 46 deletions
+35 -11
View File
@@ -5756,6 +5756,7 @@ fn repository_files_with_status(
repo: &Path,
status: &GitStatus,
) -> Result<Vec<GitRepositoryFile>, String> {
let status_index = file_status_index(&status.files);
let mut files = BTreeMap::<String, GitRepositoryFile>::new();
let tracked_output = run_git(repo, ["ls-files", "-z", "-t", "--cached", "--deleted"])?;
@@ -5772,7 +5773,7 @@ fn repository_files_with_status(
continue;
}
let path = String::from_utf8_lossy(path_bytes).into_owned();
let status = status_for_file(&status.files, &path);
let status = status_index.get(path.as_str()).copied().flatten();
files.insert(
path.clone(),
GitRepositoryFile {
@@ -5785,7 +5786,7 @@ fn repository_files_with_status(
let untracked_output = run_git(repo, ["ls-files", "-z", "--others", "--exclude-standard"])?;
for path in parse_nul_paths(&untracked_output) {
let status = status_for_file(&status.files, &path).or(Some(FileStatusKind::Untracked));
let status = status_index.get(path.as_str()).copied().flatten().or(Some(FileStatusKind::Untracked));
files.insert(
path.clone(),
GitRepositoryFile {
@@ -6151,16 +6152,23 @@ fn parse_nul_paths(output: &[u8]) -> Vec<String> {
.collect()
}
fn status_for_file(statuses: &[GitFileStatus], path: &str) -> Option<FileStatusKind> {
let status = find_status(statuses, path)?;
if matches!(status.staged, Some(FileStatusKind::Conflicted))
|| matches!(status.unstaged, Some(FileStatusKind::Conflicted))
{
return Some(FileStatusKind::Conflicted);
fn file_status_index(statuses: &[GitFileStatus]) -> std::collections::HashMap<&str, Option<FileStatusKind>> {
let mut index = std::collections::HashMap::with_capacity(statuses.len());
for status in statuses {
let kind = if matches!(status.staged, Some(FileStatusKind::Conflicted))
|| matches!(status.unstaged, Some(FileStatusKind::Conflicted))
{
Some(FileStatusKind::Conflicted)
} else {
status.unstaged.or(status.staged)
};
// Match find_status: the first record wins, including rename aliases.
index.entry(status.path.as_str()).or_insert(kind);
if let Some(old_path) = status.old_path.as_deref() {
index.entry(old_path).or_insert(kind);
}
}
status.unstaged.or(status.staged)
index
}
fn has_unresolved_conflicts(status: &GitStatus) -> bool {
@@ -10115,6 +10123,22 @@ mod tests {
assert_eq!(docs.replace("\r\n", "\n"), "docs2\n");
}
#[test]
fn file_status_index_preserves_rename_conflict_and_first_match_semantics() {
let statuses = vec![
GitFileStatus { path: "new".into(), old_path: Some("old".into()), staged: Some(FileStatusKind::Renamed), unstaged: Some(FileStatusKind::Modified) },
GitFileStatus { path: "old".into(), old_path: None, staged: Some(FileStatusKind::Added), unstaged: None },
GitFileStatus { path: "conflict".into(), old_path: None, staged: Some(FileStatusKind::Conflicted), unstaged: Some(FileStatusKind::Modified) },
GitFileStatus { path: "empty".into(), old_path: None, staged: None, unstaged: None },
];
let index = file_status_index(&statuses);
assert_eq!(index.get("new"), Some(&Some(FileStatusKind::Modified)));
assert_eq!(index.get("old"), Some(&Some(FileStatusKind::Modified)));
assert_eq!(index.get("conflict"), Some(&Some(FileStatusKind::Conflicted)));
assert_eq!(index.get("empty"), Some(&None));
assert_eq!(index.get("missing"), None);
}
#[test]
fn repository_files_include_tracked_deleted_and_untracked_entries() {
let repo = init_temp_repo("repository_files");