//! 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> { 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> { 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> { 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 { let refs = self.commit_refs(oid)?; Ok(!refs.is_empty()) } pub fn commit_is_default_tip(&self, oid: &CommitOid) -> GitResult { 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> { 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 { let refs = self.commit_refs(oid)?; Ok(refs.len()) } /// Returns all refs (branches + tags) grouped by commit OID. pub fn refs_grouped(&self) -> GitResult, Vec)>> { let mut map: HashMap, Vec)> = 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> { 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) } }