This commit is contained in:
2026-08-14 10:55:42 +07:00
parent 6b3e2945e0
commit c1c7ddbca2
8 changed files with 796 additions and 579 deletions
+47 -25
View File
@@ -240,17 +240,24 @@ fn open_with_cache(workdir: &Path) -> Result<gix::Repository> {
Ok(repo)
}
/// A [`FileCommit`] from a walk commit: author, message title and shortened id.
fn file_commit(commit: &gix::Commit<'_>) -> Result<FileCommit> {
/// A [`FileCommit`] from a walk commit: author, message title and shortened
/// id. `include_description` controls whether the message body is copied;
/// history lists never display it, so skipping it saves a string allocation
/// per listed commit (the diff panel fetches the full commit on demand).
fn file_commit(commit: &gix::Commit<'_>, include_description: bool) -> Result<FileCommit> {
let author = commit.author()?;
let message = commit.message()?;
Ok(FileCommit {
id: commit.id().shorten_or_id().to_string(),
summary: String::from_utf8_lossy(message.title).trim().to_string(),
description: message
.body
.map(|body| String::from_utf8_lossy(body).trim().to_string())
.filter(|body| !body.is_empty()),
description: if include_description {
message
.body
.map(|body| String::from_utf8_lossy(body).trim().to_string())
.filter(|body| !body.is_empty())
} else {
None
},
author: String::from_utf8_lossy(author.name).trim().to_string(),
time: author.time()?.seconds,
})
@@ -332,7 +339,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)?));
found.push((rel.clone(), file_commit(&commit, true)?));
pending.swap_remove(ix);
} else {
ix += 1;
@@ -381,7 +388,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()?)?);
commits.push(file_commit(&info.object()?, false)?);
}
}
Ok(CommitList { total, commits })
@@ -681,23 +688,29 @@ pub fn head_commit(repo: &gix::Repository) -> Result<Option<FileCommit>> {
return Ok(None);
};
let commit = head.object()?.into_commit();
let author = commit.author()?;
let message = commit.message()?;
Ok(Some(FileCommit {
id: commit.id().shorten_or_id().to_string(),
summary: String::from_utf8_lossy(message.title).trim().to_string(),
description: message
.body
.map(|body| String::from_utf8_lossy(body).trim().to_string())
.filter(|body| !body.is_empty()),
author: String::from_utf8_lossy(author.name).trim().to_string(),
time: author.time()?.seconds,
}))
Ok(Some(file_commit(&commit, true)?))
}
/// Short names of local branches (`refs/heads/*`), sorted alphabetically.
pub fn worktree_branches(workdir: &Path) -> Result<Vec<String>> {
/// Full metadata of the commit `id` (short or full) in the repository at
/// `workdir`, like [`head_commit`] for an arbitrary commit. Returns
/// `Ok(None)` when the id cannot be resolved.
///
/// The commit list ([`all_commits`]) omits message bodies to keep the walk
/// cheap; the diff panel uses this to fetch the full commit on demand.
pub fn worktree_commit(workdir: &Path, id: &str) -> Result<Option<FileCommit>> {
let repo = open_with_cache(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)?))
}
Err(_) => Ok(None),
}
}
/// Short names of local branches (`refs/heads/*`) of `repo`, sorted
/// alphabetically.
pub fn repo_branches(repo: &gix::Repository) -> Result<Vec<String>> {
let mut names = Vec::new();
for reference in repo.references()?.local_branches()? {
let reference = reference.map_err(|error| anyhow::anyhow!("{error}"))?;
@@ -707,9 +720,8 @@ pub fn worktree_branches(workdir: &Path) -> Result<Vec<String>> {
Ok(names)
}
/// Short names of tags (`refs/tags/*`), sorted alphabetically.
pub fn worktree_tags(workdir: &Path) -> Result<Vec<String>> {
let repo = open_with_cache(workdir)?;
/// Short names of tags (`refs/tags/*`) of `repo`, sorted alphabetically.
pub fn repo_tags(repo: &gix::Repository) -> Result<Vec<String>> {
let mut names = Vec::new();
for reference in repo.references()?.tags()? {
let reference = reference.map_err(|error| anyhow::anyhow!("{error}"))?;
@@ -719,6 +731,16 @@ pub fn worktree_tags(workdir: &Path) -> Result<Vec<String>> {
Ok(names)
}
/// Short names of local branches (`refs/heads/*`), sorted alphabetically.
pub fn worktree_branches(workdir: &Path) -> Result<Vec<String>> {
repo_branches(&open_with_cache(workdir)?)
}
/// Short names of tags (`refs/tags/*`), sorted alphabetically.
pub fn worktree_tags(workdir: &Path) -> Result<Vec<String>> {
repo_tags(&open_with_cache(workdir)?)
}
/// Short name of the branch HEAD points to, or `None` when detached (e.g.
/// after checking out a tag or a commit directly).
pub fn current_branch(repo: &gix::Repository) -> Result<Option<String>> {