219 lines
6.7 KiB
Rust
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
|
|
}
|