feat(history): add visible-parent resolver and status optimizations
Introduce an iterative visibleParentResolver for commit-graph rendering, replacing a recursive traversal. It preserves first-parent ordering, deduplicates converging paths, and avoids stack overflows; a test suite validates correctness and performance. Additionally, streamline background fetch handling in the UI to reuse fetchRemote's returned status and avoid unnecessary status reads and tab invalidation, and refactor Git status handling in Rust to build a file_status_index that preserves rename and first-match semantics while speeding lookups. - Add visibleParentResolver + comprehensive tests for ancestry handling - Reuse fetchRemote result to apply status and skip unchanged updates - Replace status_for_file with file_status_index to improve performance
This commit is contained in:
+35
-11
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user