add commit browse

This commit is contained in:
2026-08-12 08:29:00 +07:00
parent 6f381c68c3
commit 5f38c08331
3 changed files with 376 additions and 85 deletions
+64
View File
@@ -273,6 +273,42 @@ pub fn worktree_last_commit(workdir: &Path, rel: &Path) -> Result<Option<FileCom
last_commit(&gix::open(workdir)?, rel)
}
/// All commits reachable from `HEAD`, newest first, with author and summary.
/// Returns `Ok(vec![])` for a repository without any commits yet.
pub fn all_commits(repo: &gix::Repository) -> Result<Vec<FileCommit>> {
use gix::traverse::commit::simple::CommitTimeOrder;
let Some(head) = repo.head_id().ok() else {
return Ok(Vec::new());
};
let walk = repo
.rev_walk([head])
.sorting(gix::revision::walk::Sorting::ByCommitTime(
CommitTimeOrder::NewestFirst,
));
let mut commits = Vec::new();
for info in walk.all()? {
let info = info?;
let commit = info.object()?;
let author = commit.author()?;
let message = commit.message()?;
commits.push(FileCommit {
id: commit.id().shorten_or_id().to_string(),
summary: String::from_utf8_lossy(message.title).trim().to_string(),
author: String::from_utf8_lossy(author.name).trim().to_string(),
time: author.time()?.seconds,
});
}
Ok(commits)
}
/// Like [`all_commits`], but opens the repository located at `workdir`
/// (for non-bare clones the clone root is the worktree) first.
pub fn worktree_all_commits(workdir: &Path) -> Result<Vec<FileCommit>> {
all_commits(&gix::open(workdir)?)
}
fn collect_entries(root: &Path, dir: &Path, out: &mut Vec<(PathBuf, bool)>) -> Result<()> {
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
@@ -436,6 +472,34 @@ mod tests {
assert!(commit.time > 0);
}
#[test]
fn all_commits_lists_every_commit() {
let (dir, repo) = fixture(&[("a.txt", b"one")]);
commit_all(&repo, "initial");
std::fs::write(dir.path().join("a.txt"), b"two").expect("write");
commit_all(&repo, "second");
std::fs::write(dir.path().join("b.txt"), b"b").expect("write");
commit_all(&repo, "third");
let commits = all_commits(&repo).expect("commits");
let mut summaries: Vec<&str> = commits.iter().map(|c| c.summary.as_str()).collect();
summaries.sort();
assert_eq!(summaries, vec!["initial", "second", "third"]);
assert!(
commits
.iter()
.all(|c| c.author == "Test Author" && !c.id.is_empty() && c.time > 0)
);
}
#[test]
fn all_commits_returns_empty_without_head() {
let (_dir, repo) = fixture(&[("a.txt", b"one")]);
assert!(all_commits(&repo).expect("commits").is_empty());
}
#[test]
fn last_commit_returns_none_for_untracked_files() {
let (dir, repo) = fixture(&[("a.txt", b"one")]);