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

218 lines
7.6 KiB
Rust

//! Commit reference (branch/tag) and reflog operations.
use std::collections::HashMap;
use crate::commit::types::*;
use crate::{GitDomain, GitError, GitResult};
impl GitDomain {
pub fn commit_refs(&self, oid: &CommitOid) -> GitResult<Vec<CommitRefInfo>> {
let _ = self
.repo()
.find_commit(oid.to_oid()?)
.map_err(|_e| GitError::ObjectNotFound(oid.to_string()))?;
let mut refs = Vec::new();
for branch_result in self
.repo()
.branches(Some(git2::BranchType::Local))
.map_err(|e| GitError::Internal(e.to_string()))?
{
let (branch, _) = branch_result.map_err(|e| GitError::Internal(e.to_string()))?;
if let Some(target) = branch.get().target() {
if target == oid.to_oid()? {
let name = branch
.name()
.map_err(|e| GitError::Internal(e.to_string()))?
.unwrap_or("")
.to_string();
refs.push(CommitRefInfo {
name,
target: oid.clone(),
is_remote: false,
is_tag: false,
});
}
}
}
if let Ok(walk) = self.repo.references() {
for ref_result in walk {
if let Ok(r) = ref_result {
if let Some(name) = r.name() {
if name.starts_with("refs/tags/") {
if let Some(target) = r.target() {
if target == oid.to_oid()? {
refs.push(CommitRefInfo {
name: name.to_string(),
target: oid.clone(),
is_remote: false,
is_tag: true,
});
}
}
}
}
}
}
}
Ok(refs)
}
pub fn commit_branches(&self, oid: &CommitOid) -> GitResult<Vec<String>> {
let refs = self.commit_refs(oid)?;
Ok(refs
.into_iter()
.filter(|r| !r.is_remote && !r.is_tag)
.map(|r| r.name)
.collect())
}
pub fn commit_tags(&self, oid: &CommitOid) -> GitResult<Vec<String>> {
let refs = self.commit_refs(oid)?;
Ok(refs
.into_iter()
.filter(|r| r.is_tag)
.map(|r| r.name)
.collect())
}
pub fn commit_is_tip(&self, oid: &CommitOid) -> GitResult<bool> {
let refs = self.commit_refs(oid)?;
Ok(!refs.is_empty())
}
pub fn commit_is_default_tip(&self, oid: &CommitOid) -> GitResult<bool> {
let default = self.repo.head().ok().and_then(|r| r.target());
Ok(default == Some(oid.to_oid()?))
}
pub fn commit_reflog(
&self,
oid: &CommitOid,
refname: Option<&str>,
) -> GitResult<Vec<CommitReflogEntry>> {
let refname = match refname {
Some(name) => name.to_string(),
None => self
.repo()
.head()
.ok()
.and_then(|r| r.name().map(|n| n.to_owned()))
.ok_or_else(|| GitError::Internal("HEAD has no name".to_string()))?,
};
let reflog = self
.repo()
.reflog(&refname)
.map_err(|e| GitError::Internal(e.to_string()))?;
let mut entries = Vec::new();
let oid_val = oid.to_oid()?;
for entry in reflog.iter() {
if entry.id_new() == oid_val || entry.id_old() == oid_val {
let sig = entry.committer();
entries.push(CommitReflogEntry {
oid_new: CommitOid::from_git2(entry.id_new()),
oid_old: CommitOid::from_git2(entry.id_old()),
committer_name: sig.name().unwrap_or("").to_string(),
committer_email: sig.email().unwrap_or("").to_string(),
time_secs: sig.when().seconds(),
message: entry.message().map(String::from),
ref_name: refname.clone(),
});
}
}
Ok(entries)
}
pub fn commit_ref_count(&self, oid: &CommitOid) -> GitResult<usize> {
let refs = self.commit_refs(oid)?;
Ok(refs.len())
}
/// Returns all refs (branches + tags) grouped by commit OID.
pub fn refs_grouped(&self) -> GitResult<HashMap<String, (Vec<String>, Vec<String>)>> {
let mut map: HashMap<String, (Vec<String>, Vec<String>)> = HashMap::new();
for branch_result in self
.repo()
.branches(Some(git2::BranchType::Local))
.map_err(|e| GitError::Internal(e.to_string()))?
{
let (branch, _) = branch_result.map_err(|e| GitError::Internal(e.to_string()))?;
if let Some(target) = branch.get().target() {
let oid_str = target.to_string();
let name = branch
.name()
.map_err(|e| GitError::Internal(e.to_string()))?
.unwrap_or("")
.to_string();
let entry = map
.entry(oid_str)
.or_insert_with(|| (Vec::new(), Vec::new()));
entry.0.push(name);
}
}
if let Ok(walk) = self.repo.references() {
for ref_result in walk {
if let Ok(r) = ref_result {
if let Some(name) = r.name() {
if name.starts_with("refs/tags/") {
if let Some(target) = r.target() {
let oid_str = target.to_string();
let full_name = name.to_string();
let entry = map
.entry(oid_str)
.or_insert_with(|| (Vec::new(), Vec::new()));
entry.1.push(full_name);
}
}
}
}
}
}
Ok(map)
}
/// Returns all reflog entries for a given ref (defaults to HEAD).
pub fn reflog_entries(&self, refname: Option<&str>) -> GitResult<Vec<CommitReflogEntry>> {
let refname = match refname {
Some(name) => name.to_string(),
None => self
.repo()
.head()
.ok()
.and_then(|r| r.name().map(|n| n.to_owned()))
.ok_or_else(|| GitError::Internal("HEAD has no name".to_string()))?,
};
let reflog = self
.repo()
.reflog(&refname)
.map_err(|e| GitError::Internal(e.to_string()))?;
let mut entries = Vec::new();
for entry in reflog.iter() {
let sig = entry.committer();
entries.push(CommitReflogEntry {
oid_new: CommitOid::from_git2(entry.id_new()),
oid_old: CommitOid::from_git2(entry.id_old()),
committer_name: sig.name().unwrap_or("").to_string(),
committer_email: sig.email().unwrap_or("").to_string(),
time_secs: sig.when().seconds(),
message: entry.message().map(String::from),
ref_name: refname.clone(),
});
}
Ok(entries)
}
}