diff --git a/desktop/src-tauri/src/commands/project_git.rs b/desktop/src-tauri/src/commands/project_git.rs index 38dbcd87d..597ebaedd 100644 --- a/desktop/src-tauri/src/commands/project_git.rs +++ b/desktop/src-tauri/src/commands/project_git.rs @@ -9,8 +9,7 @@ use serde::Serialize; use std::time::UNIX_EPOCH; use tauri::State; -// Bound eager snapshot payloads without truncating the repository tree. Later -// paths remain browseable without embedded content. +// Bound eager content without truncating the repository tree. const MAX_EAGER_FILE_PREVIEWS: usize = 250; #[cfg(test)] @@ -260,16 +259,16 @@ fn parse_worktree_files( output .split('\0') .filter(|path| !path.trim().is_empty()) - .enumerate() - .filter_map(|(index, path)| { + .filter_map(|path| { let full_path = repo_dir.join(path); let metadata = std::fs::metadata(&full_path).ok()?; - if !metadata.is_file() { - return None; - } + metadata.is_file().then_some((path, full_path, metadata)) + }) + .enumerate() + .map(|(index, (path, full_path, metadata))| { let size = Some(metadata.len()); let latest_commit = latest_commit_by_path.get(path).cloned(); - Some(ProjectRepoFileInfo { + ProjectRepoFileInfo { path: path.to_string(), kind: "blob".to_string(), size, @@ -281,7 +280,7 @@ fn parse_worktree_files( .map(|commit| commit.timestamp) .or_else(|| path_modified_at(&full_path)), latest_commit, - }) + } }) .collect() } @@ -325,21 +324,22 @@ fn parse_ls_tree( output: &str, latest_commit_by_path: &std::collections::HashMap, ) -> Vec { + let mut blob_index = 0; output .lines() - .enumerate() - .filter_map(|(index, line)| { + .filter_map(|line| { let (meta, path) = line.split_once('\t')?; let mut parts = meta.split_whitespace(); let _mode = parts.next()?; let kind = parts.next()?.to_string(); let _object = parts.next()?; let size = parts.next().and_then(|value| value.parse::().ok()); - let preview_content = if kind == "blob" && index < MAX_EAGER_FILE_PREVIEWS { - read_preview_content(repo_dir, path, size) - } else { - None - }; + if kind == "blob" { + blob_index += 1; + } + let preview_content = (kind == "blob" && blob_index <= MAX_EAGER_FILE_PREVIEWS) + .then(|| read_preview_content(repo_dir, path, size)) + .flatten(); Some(ProjectRepoFileInfo { path: path.to_string(), kind, diff --git a/desktop/src-tauri/src/commands/project_git_tests.rs b/desktop/src-tauri/src/commands/project_git_tests.rs index 6e597cc6d..d9ddd9b81 100644 --- a/desktop/src-tauri/src/commands/project_git_tests.rs +++ b/desktop/src-tauri/src/commands/project_git_tests.rs @@ -28,3 +28,54 @@ fn parse_ls_tree_keeps_paths_after_eager_preview_limit() { Some("src/application.rs") ); } + +#[test] +fn parse_ls_tree_counts_only_blobs_toward_eager_preview_limit() { + let repo_dir = tempfile::tempdir().expect("create temporary repository"); + std::fs::write(repo_dir.path().join("application.rs"), "fn main() {}") + .expect("write preview file"); + let non_blob_entries = (0..MAX_EAGER_FILE_PREVIEWS) + .map(|index| { + format!( + "160000 commit {} -\tvendor/dependency-{index:03}", + "a".repeat(40) + ) + }) + .collect::>() + .join("\n"); + let output = format!( + "{non_blob_entries}\n100644 blob {} 12\tapplication.rs", + "b".repeat(40) + ); + + let files = parse_ls_tree(repo_dir.path(), &output, &std::collections::HashMap::new()); + + assert_eq!( + files + .last() + .and_then(|file| file.preview_content.as_deref()), + Some("fn main() {}") + ); +} + +#[test] +fn parse_worktree_files_counts_only_files_toward_eager_preview_limit() { + let repo_dir = tempfile::tempdir().expect("create temporary repository"); + std::fs::create_dir(repo_dir.path().join("directory")).expect("create directory"); + let paths = (0..MAX_EAGER_FILE_PREVIEWS) + .map(|index| { + let path = format!("file-{index:03}.txt"); + std::fs::write(repo_dir.path().join(&path), "preview").expect("write preview file"); + path + }) + .collect::>(); + let output = std::iter::once("directory") + .chain(paths.iter().map(String::as_str)) + .collect::>() + .join("\0"); + + let files = parse_worktree_files(repo_dir.path(), &output, &std::collections::HashMap::new()); + + assert_eq!(files.len(), MAX_EAGER_FILE_PREVIEWS); + assert!(files.iter().all(|file| file.preview_content.is_some())); +} diff --git a/desktop/src/features/projects/ui/ProjectRepositoryPanel.tsx b/desktop/src/features/projects/ui/ProjectRepositoryPanel.tsx index 8c69613d8..7ee3a98b1 100644 --- a/desktop/src/features/projects/ui/ProjectRepositoryPanel.tsx +++ b/desktop/src/features/projects/ui/ProjectRepositoryPanel.tsx @@ -649,6 +649,11 @@ export function RepositoryFilesPanel({ [currentPath, files], ); const visibleEntries = entries.slice(0, visibleEntryCount); + const nextVisibleEntryCount = nextRepositoryEntryLimit( + visibleEntryCount, + entries.length, + ); + const nextEntryCount = nextVisibleEntryCount - visibleEntries.length; const latestCommit = snapshot?.latestCommit ?? null; const knownLatestCommitProfile = React.useMemo( () => profileForCommitAuthor(latestCommit, profiles), @@ -918,10 +923,11 @@ export function RepositoryFilesPanel({ {entries.length > visibleEntries.length ? (
- + Showing {visibleEntries.length} of {entries.length} entries.
) : null}