218 lines
7.6 KiB
Rust
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)
|
|
}
|
|
}
|