gitdataai/libs/git/commit/traverse.rs
2026-04-15 09:08:09 +08:00

219 lines
6.7 KiB
Rust

//! Commit traversal and iteration.
use crate::commit::types::*;
use crate::{GitDomain, GitError, GitResult};
#[derive(Debug, Clone)]
pub struct CommitWalkOptions {
pub rev: Option<String>,
pub sort: CommitSort,
pub limit: usize,
pub first_parent_only: bool,
}
impl CommitWalkOptions {
pub fn new() -> Self {
Self {
rev: None,
sort: CommitSort(git2::Sort::TOPOLOGICAL.bits() | git2::Sort::TIME.bits()),
limit: 0,
first_parent_only: false,
}
}
pub fn rev(mut self, rev: &str) -> Self {
self.rev = Some(rev.to_string());
self
}
pub fn topological(mut self) -> Self {
self.sort = CommitSort(git2::Sort::TOPOLOGICAL.bits());
self
}
pub fn time_order(mut self) -> Self {
self.sort = CommitSort(git2::Sort::TIME.bits());
self
}
pub fn reverse(mut self) -> Self {
self.sort = CommitSort(self.sort.0 | git2::Sort::REVERSE.bits());
self
}
pub fn limit(mut self, n: usize) -> Self {
self.limit = n;
self
}
pub fn first_parent(mut self) -> Self {
self.first_parent_only = true;
self
}
}
impl Default for CommitWalkOptions {
fn default() -> Self {
Self::new()
}
}
impl GitDomain {
pub fn commit_walk(&self, opts: CommitWalkOptions) -> GitResult<Vec<CommitMeta>> {
let mut revwalk = self
.repo()
.revwalk()
.map_err(|e| GitError::Internal(e.to_string()))?;
revwalk
.set_sorting(opts.sort.to_git2())
.map_err(|e| GitError::Internal(e.to_string()))?;
if let Some(ref r) = opts.rev {
if r.contains("..") {
revwalk
.push_range(r)
.map_err(|e| GitError::Internal(e.to_string()))?;
} else {
revwalk
.push_ref(r)
.map_err(|e| GitError::Internal(e.to_string()))?;
}
} else {
revwalk
.push_head()
.map_err(|e| GitError::Internal(e.to_string()))?;
}
let mut commits = Vec::new();
if opts.first_parent_only {
let mut prev_oid: Option<git2::Oid> = None;
for oid_result in revwalk {
let oid = oid_result.map_err(|e| GitError::Internal(e.to_string()))?;
if let Some(prev) = prev_oid {
if let Ok(commit) = self.repo.find_commit(oid) {
if commit.parent_ids().next() == Some(prev) {
if limit_check(&commits, opts.limit) {
break;
}
commits.push(CommitMeta::from_git2(&commit));
prev_oid = Some(oid);
}
}
} else {
if let Ok(commit) = self.repo.find_commit(oid) {
if limit_check(&commits, opts.limit) {
break;
}
commits.push(CommitMeta::from_git2(&commit));
prev_oid = Some(oid);
}
}
}
} else {
for oid_result in revwalk {
let oid = oid_result.map_err(|e| GitError::Internal(e.to_string()))?;
if limit_check(&commits, opts.limit) {
break;
}
if let Ok(commit) = self.repo.find_commit(oid) {
commits.push(CommitMeta::from_git2(&commit));
}
}
}
Ok(commits)
}
pub fn commit_topo_walk(&self, rev: Option<&str>, limit: usize) -> GitResult<Vec<CommitMeta>> {
self.commit_walk(CommitWalkOptions {
rev: rev.map(String::from),
sort: CommitSort(git2::Sort::TOPOLOGICAL.bits() | git2::Sort::TIME.bits()),
limit,
first_parent_only: false,
})
}
pub fn commit_reverse_walk(
&self,
rev: Option<&str>,
limit: usize,
) -> GitResult<Vec<CommitMeta>> {
self.commit_walk(CommitWalkOptions {
rev: rev.map(String::from),
sort: CommitSort(git2::Sort::TIME.bits() | git2::Sort::REVERSE.bits()),
limit,
first_parent_only: false,
})
}
pub fn commit_mainline(&self, rev: Option<&str>, limit: usize) -> GitResult<Vec<CommitMeta>> {
self.commit_walk(CommitWalkOptions {
rev: rev.map(String::from),
sort: CommitSort(git2::Sort::TOPOLOGICAL.bits() | git2::Sort::TIME.bits()),
limit,
first_parent_only: true,
})
}
pub fn commit_ancestors(&self, oid: &CommitOid, limit: usize) -> GitResult<Vec<CommitMeta>> {
let mut revwalk = self
.repo()
.revwalk()
.map_err(|e| GitError::Internal(e.to_string()))?;
revwalk
.set_sorting(git2::Sort::TOPOLOGICAL | git2::Sort::TIME)
.map_err(|e| GitError::Internal(e.to_string()))?;
revwalk
.push(oid.to_oid()?)
.map_err(|e| GitError::Internal(e.to_string()))?;
let mut commits = Vec::new();
for oid_result in revwalk {
let oid = oid_result.map_err(|e| GitError::Internal(e.to_string()))?;
if limit > 0 && commits.len() >= limit {
break;
}
if let Ok(commit) = self.repo.find_commit(oid) {
commits.push(CommitMeta::from_git2(&commit));
}
}
Ok(commits)
}
pub fn commit_descendants(&self, oid: &CommitOid, limit: usize) -> GitResult<Vec<CommitMeta>> {
let range = format!("{}..", oid);
self.commit_walk(CommitWalkOptions {
rev: Some(range),
sort: CommitSort(git2::Sort::TOPOLOGICAL.bits() | git2::Sort::TIME.bits()),
limit,
first_parent_only: false,
})
}
pub fn resolve_rev(&self, rev: &str) -> GitResult<CommitOid> {
if let Ok(oid) = git2::Oid::from_str(rev) {
return Ok(CommitOid::from_git2(oid));
}
if let Ok(reference) = self.repo.find_reference(rev) {
if let Some(target) = reference.target() {
return Ok(CommitOid::from_git2(target));
}
}
if let Ok(commit) = self.repo.revparse_single(rev) {
return Ok(CommitOid::from_git2(commit.id()));
}
Err(GitError::InvalidOid(format!("cannot resolve: {}", rev)))
}
}
fn limit_check(commits: &[CommitMeta], limit: usize) -> bool {
limit > 0 && commits.len() >= limit
}