This commit is contained in:
2026-09-04 17:12:52 +07:00
parent 1d224218df
commit 1496b7afeb
24 changed files with 270 additions and 645 deletions
+36 -21
View File
@@ -71,7 +71,7 @@ const SCAN_MAX_DEPTH: usize = 12;
/// Directories never descended into during a scan.
///
/// Dependency caches can be enormous without ever containing user repositories.
const SCAN_SKIPPED_DIRS: [&str; 1] = ["node_modules"];
const SCAN_SKIPPED_DIR: &str = "node_modules";
/// Walk `root` recursively and collect the paths of git repositories below it.
pub fn find_git_repos(root: &Path) -> Vec<PathBuf> {
@@ -107,7 +107,7 @@ pub fn find_git_repos(root: &Path) -> Vec<PathBuf> {
}
let file_name = entry.file_name();
let name = file_name.to_string_lossy();
if name.starts_with('.') || SCAN_SKIPPED_DIRS.contains(&name.as_ref()) {
if name.starts_with('.') || name == SCAN_SKIPPED_DIR {
continue;
}
stack.push((entry.path(), depth + 1));
@@ -708,7 +708,8 @@ pub fn sanitize_path_component(id: &str) -> String {
/// In-memory object cache for history walks, see [`open_with_cache`].
///
/// Without one, every walk re-decodes the same commit objects from the object database.
/// Without one, a walk re-decodes the same commit objects from the object database.
/// Sized generously: a walk can cover a large portion of the repository's history.
const OBJECT_CACHE_BYTES: usize = 64 * 1024 * 1024;
/// Metadata of a commit, as shown in the repository browser's file header.
@@ -797,21 +798,35 @@ pub fn find_readme(repo: &gix::Repository) -> Result<Option<PathBuf>> {
.and_then(|path| path.strip_prefix(workdir).ok().map(Path::to_path_buf)))
}
/// Open the repository at `workdir` with an in-memory object cache sized for history walks.
/// Open the repository at `workdir` with an in-memory object cache.
///
/// Only history walks use it, they re-decode the same commit objects repeatedly.
/// Single-object reads open the repository plain.
fn open_with_cache(workdir: &Path) -> Result<gix::Repository> {
let mut repo = gix::open(workdir)?;
repo.object_cache_size_if_unset(OBJECT_CACHE_BYTES);
Ok(repo)
}
/// A [`FileCommit`] from a walk commit, with author, message title and shortened id.
/// `include_description` controls whether the message body is copied.
///
/// History lists never display it,
/// so skipping it saves an allocation per listed commit.
/// A [`FileCommit`] from a commit, with author, message title, body and shortened id.
///
/// The diff panel fetches the full commit on demand.
fn file_commit(commit: &gix::Commit<'_>, include_description: bool) -> Result<FileCommit> {
fn file_commit(commit: &gix::Commit<'_>) -> Result<FileCommit> {
file_commit_with_description(commit, true)
}
/// A [`FileCommit`] without the message body, for history lists that never display it.
///
/// Skipping the body saves an allocation per listed commit.
fn file_commit_summary(commit: &gix::Commit<'_>) -> Result<FileCommit> {
file_commit_with_description(commit, false)
}
/// [`file_commit`] and [`file_commit_summary`], `include_description` picks the body.
fn file_commit_with_description(
commit: &gix::Commit<'_>,
include_description: bool,
) -> Result<FileCommit> {
let author = commit.author()?;
let message = commit.message()?;
Ok(FileCommit {
@@ -903,7 +918,7 @@ fn last_commits(repo: &gix::Repository, rels: &[PathBuf]) -> Result<Vec<(PathBuf
if blob.map(|entry| entry.id().detach()) != parent_blob.map(|entry| entry.id().detach())
{
found.push((rel.clone(), file_commit(&commit, true)?));
found.push((rel.clone(), file_commit(&commit)?));
pending.swap_remove(ix);
} else {
ix += 1;
@@ -952,7 +967,7 @@ pub fn all_commits(repo: &gix::Repository) -> Result<CommitList> {
let info = info?;
total += 1;
if commits.len() < MAX_LISTED_COMMITS {
commits.push(file_commit(&info.object()?, false)?);
commits.push(file_commit_summary(&info.object()?)?);
}
}
Ok(CommitList { total, commits })
@@ -1040,7 +1055,7 @@ pub struct CommitDiff {
///
/// Compared against its first parent, the empty tree for the root commit.
pub fn worktree_commit_diff(workdir: &Path, id: &str) -> Result<CommitDiff> {
commit_diff(&open_with_cache(workdir)?, id)
commit_diff(&gix::open(workdir)?, id)
}
fn commit_diff(repo: &gix::Repository, id: &str) -> Result<CommitDiff> {
@@ -1058,7 +1073,7 @@ fn commit_diff(repo: &gix::Repository, id: &str) -> Result<CommitDiff> {
///
/// Directories and submodules are skipped, files are sorted by path.
pub fn worktree_commit_range_diff(workdir: &Path, base: &str, tip: &str) -> Result<CommitDiff> {
let repo = open_with_cache(workdir)?;
let repo = gix::open(workdir)?;
let base_tree = repo
.rev_parse_single(base.as_bytes())?
.object()?
@@ -1093,7 +1108,7 @@ pub fn worktree_commit_range_commits(
let mut commits = Vec::new();
for info in walk.all()? {
let info = info?;
commits.push(file_commit(&info.object()?, false)?);
commits.push(file_commit_summary(&info.object()?)?);
}
Ok(commits)
}
@@ -1667,7 +1682,7 @@ pub fn head_commit(repo: &gix::Repository) -> Result<Option<FileCommit>> {
return Ok(None);
};
let commit = head.object()?.into_commit();
Ok(Some(file_commit(&commit, true)?))
Ok(Some(file_commit(&commit)?))
}
/// Full metadata of the commit `id`, short or full, in the repository at `workdir`.
@@ -1675,11 +1690,11 @@ pub fn head_commit(repo: &gix::Repository) -> Result<Option<FileCommit>> {
///
/// `Ok(None)` when the id cannot be resolved.
pub fn worktree_commit(workdir: &Path, id: &str) -> Result<Option<FileCommit>> {
let repo = open_with_cache(workdir)?;
let repo = gix::open(workdir)?;
match repo.rev_parse_single(id.as_bytes()) {
Ok(commit_id) => {
let commit = commit_id.object()?.into_commit();
Ok(Some(file_commit(&commit, true)?))
Ok(Some(file_commit(&commit)?))
}
Err(_) => Ok(None),
}
@@ -1709,7 +1724,7 @@ pub fn repo_tags(repo: &gix::Repository) -> Result<Vec<String>> {
/// Short names of local branches, `refs/heads/*`, sorted alphabetically.
pub fn worktree_branches(workdir: &Path) -> Result<Vec<String>> {
repo_branches(&open_with_cache(workdir)?)
repo_branches(&gix::open(workdir)?)
}
/// Short name of the branch HEAD points to, or `None` when detached.
@@ -1770,7 +1785,7 @@ pub fn repo_ref_state(repo: &gix::Repository) -> Result<RepoRefState> {
/// [`repo_ref_state`] for the repository at `workdir`.
pub fn worktree_ref_state(workdir: &Path) -> Result<RepoRefState> {
repo_ref_state(&open_with_cache(workdir)?)
repo_ref_state(&gix::open(workdir)?)
}
/// Everything the browser needs to refresh after a branch or tag switch.
@@ -1791,7 +1806,7 @@ pub struct WorktreeSnapshot {
///
/// Collects entries, the README, the branch HEAD points to and its commit.
pub fn worktree_snapshot(workdir: &Path) -> Result<WorktreeSnapshot> {
let repo = open_with_cache(workdir)?;
let repo = gix::open(workdir)?;
let readme_path = find_readme(&repo)?;
let readme = match &readme_path {
Some(path) => worktree_read(&repo, path)?,