gitdataai/lib/git/cmd/commit/commit_walker.rs

154 lines
4.8 KiB
Rust

use serde::{Deserialize, Serialize};
use crate::{
bare::GitBare,
cmd::{commit::CommitMeta, oid::ObjectId},
errors::{GitError, GitResult},
};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CommitWalkSort {
None,
Topological,
Time,
Reverse,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitWalkParams {
pub start_oids: Vec<ObjectId>,
pub hide_oids: Vec<ObjectId>,
pub limit: Option<usize>,
pub skip: usize,
pub first_parent: bool,
pub sort: CommitWalkSort,
pub branch: Option<String>,
}
impl Default for CommitWalkParams {
fn default() -> Self {
Self {
start_oids: Vec::new(),
hide_oids: Vec::new(),
limit: None,
skip: 0,
first_parent: false,
sort: CommitWalkSort::Time,
branch: None,
}
}
}
impl GitBare {
pub fn commit_walk(
&self,
params: CommitWalkParams,
) -> GitResult<Vec<CommitMeta>> {
let repo = self.gix_repo()?;
let tips: Vec<gix::hash::ObjectId> = if let Some(ref branch_name) =
params.branch
{
if branch_name.is_empty() {
vec![repo.head_id()?.detach()]
} else {
let ref_name = format!("refs/heads/{branch_name}");
match repo.try_find_reference(&ref_name)? {
Some(reference) => {
let target = reference.target();
let target_id = target.try_id().ok_or_else(|| {
GitError::Internal(format!(
"branch '{branch_name}' has no direct target"
))
})?;
vec![target_id.to_owned()]
}
None => {
let remote_ref = format!("refs/remotes/{branch_name}");
match repo.try_find_reference(&remote_ref)? {
Some(reference) => {
let target = reference.target();
let target_id = target.try_id().ok_or_else(|| {
GitError::Internal(format!("remote branch '{branch_name}' has no direct target"))
})?;
vec![target_id.to_owned()]
}
None => {
return Err(GitError::RefNotFound(
branch_name.clone(),
));
}
}
}
}
}
} else if params.start_oids.is_empty() {
vec![repo.head_id()?.detach()]
} else {
params
.start_oids
.iter()
.map(|oid| oid.try_into())
.collect::<Result<Vec<_>, _>>()?
};
let hide: Vec<gix::hash::ObjectId> = params
.hide_oids
.iter()
.map(|oid| oid.try_into())
.collect::<Result<Vec<_>, _>>()?;
let mut platform = repo.rev_walk(tips);
match params.sort {
CommitWalkSort::None => {}
CommitWalkSort::Topological => {
platform = platform
.sorting(gix::revision::walk::Sorting::BreadthFirst);
}
CommitWalkSort::Time => {
platform = platform.sorting(gix::revision::walk::Sorting::ByCommitTime(
gix::traverse::commit::simple::CommitTimeOrder::NewestFirst,
));
}
CommitWalkSort::Reverse => {
platform = platform.sorting(gix::revision::walk::Sorting::ByCommitTime(
gix::traverse::commit::simple::CommitTimeOrder::OldestFirst,
));
}
}
if params.first_parent {
platform = platform.first_parent_only();
}
if !hide.is_empty() {
platform = platform.with_boundary(hide);
}
let walk = platform.all()?;
let mut commits = Vec::new();
let mut count = 0;
let skip = params.skip;
for info in walk {
if count < skip {
count += 1;
continue;
}
let info = info?;
let oid = ObjectId::new(info.id().detach().to_hex().to_string());
let commit = self.commit_info(oid)?;
commits.push(commit);
if let Some(limit) = params.limit {
if commits.len() >= limit {
break;
}
}
count += 1;
}
if matches!(params.sort, CommitWalkSort::Reverse) {
commits.reverse();
}
Ok(commits)
}
}