151 lines
4.9 KiB
Rust
151 lines
4.9 KiB
Rust
//! Branch merge operations.
|
|
|
|
use crate::commit::types::CommitOid;
|
|
use crate::{GitDomain, GitError, GitResult};
|
|
|
|
impl GitDomain {
|
|
pub fn branch_is_merged(&self, branch: &str, into: &str) -> GitResult<bool> {
|
|
let branch_oid = self
|
|
.branch_target(branch)?
|
|
.ok_or_else(|| GitError::InvalidOid(format!("branch {} has no target", branch)))?
|
|
.to_oid()?;
|
|
let into_oid = self
|
|
.branch_target(into)?
|
|
.ok_or_else(|| GitError::InvalidOid(format!("branch {} has no target", into)))?
|
|
.to_oid()?;
|
|
|
|
self.repo
|
|
.graph_ahead_behind(into_oid, branch_oid)
|
|
.map(|(ahead, _)| ahead == 0)
|
|
.map_err(|e| GitError::Internal(e.to_string()))
|
|
}
|
|
|
|
pub fn branch_merge_base(&self, branch1: &str, branch2: &str) -> GitResult<CommitOid> {
|
|
let oid1 = self
|
|
.branch_target(branch1)?
|
|
.ok_or_else(|| GitError::InvalidOid(format!("branch {} has no target", branch1)))?
|
|
.to_oid()?;
|
|
let oid2 = self
|
|
.branch_target(branch2)?
|
|
.ok_or_else(|| GitError::InvalidOid(format!("branch {} has no target", branch2)))?
|
|
.to_oid()?;
|
|
|
|
let base = self
|
|
.repo()
|
|
.merge_base(oid1, oid2)
|
|
.map_err(|e| GitError::Internal(e.to_string()))?;
|
|
|
|
Ok(CommitOid::from_git2(base))
|
|
}
|
|
|
|
pub fn branch_is_ancestor(&self, child: &str, ancestor: &str) -> GitResult<bool> {
|
|
let child_oid = self
|
|
.branch_target(child)?
|
|
.ok_or_else(|| GitError::InvalidOid(format!("branch {} has no target", child)))?
|
|
.to_oid()?;
|
|
let ancestor_oid = self
|
|
.branch_target(ancestor)?
|
|
.ok_or_else(|| GitError::InvalidOid(format!("branch {} has no target", ancestor)))?
|
|
.to_oid()?;
|
|
|
|
self.repo
|
|
.graph_ahead_behind(child_oid, ancestor_oid)
|
|
.map(|(_, behind)| behind == 0)
|
|
.map_err(|e| GitError::Internal(e.to_string()))
|
|
}
|
|
|
|
pub fn branch_fast_forward(&self, target: &str, force: bool) -> GitResult<CommitOid> {
|
|
let target_oid = self
|
|
.branch_target(target)?
|
|
.ok_or_else(|| GitError::InvalidOid(format!("branch {} has no target", target)))?
|
|
.to_oid()?;
|
|
|
|
let head = self
|
|
.repo()
|
|
.head()
|
|
.ok()
|
|
.ok_or_else(|| GitError::Internal("HEAD is not attached".to_string()))?;
|
|
let head_oid = head
|
|
.target()
|
|
.ok_or_else(|| GitError::Internal("HEAD has no target".to_string()))?;
|
|
let head_name = head
|
|
.name()
|
|
.ok_or_else(|| GitError::Internal("HEAD has no name".to_string()))?;
|
|
|
|
let ref_name = if head_name.starts_with("refs/") {
|
|
head_name.to_string()
|
|
} else {
|
|
format!("refs/heads/{}", head_name)
|
|
};
|
|
|
|
let (ahead, behind) = self
|
|
.repo()
|
|
.graph_ahead_behind(head_oid, target_oid)
|
|
.map_err(|e| GitError::Internal(e.to_string()))?;
|
|
|
|
if behind == 0 {
|
|
return Ok(CommitOid::from_git2(head_oid));
|
|
}
|
|
|
|
if !force && ahead > 0 {
|
|
// ahead > 0 && behind > 0 means diverged; ahead == 0 && behind > 0 is valid FF
|
|
return Err(GitError::Internal(
|
|
"not a fast-forward: branches have diverged".to_string(),
|
|
));
|
|
}
|
|
|
|
self.repo
|
|
.reference_matching(
|
|
ref_name.as_str(),
|
|
target_oid,
|
|
true,
|
|
head_oid,
|
|
"fast-forward",
|
|
)
|
|
.map_err(|e| GitError::Internal(e.to_string()))?;
|
|
|
|
self.repo
|
|
.set_head_detached(target_oid)
|
|
.map_err(|e| GitError::Internal(e.to_string()))?;
|
|
|
|
self.repo
|
|
.checkout_tree(self.repo.find_commit(target_oid)?.as_object(), None)
|
|
.map_err(|e| GitError::Internal(e.to_string()))?;
|
|
|
|
Ok(CommitOid::from_git2(target_oid))
|
|
}
|
|
|
|
pub fn branch_abort_merge(&self) -> GitResult<()> {
|
|
let head_oid = self
|
|
.repo()
|
|
.head()
|
|
.ok()
|
|
.and_then(|r| r.target())
|
|
.ok_or_else(|| GitError::Internal("HEAD is not attached".to_string()))?;
|
|
|
|
let obj = self
|
|
.repo()
|
|
.find_object(head_oid, None)
|
|
.map_err(|e| GitError::Internal(e.to_string()))?;
|
|
|
|
self.repo
|
|
.reset(&obj, git2::ResetType::Hard, None)
|
|
.map_err(|e| GitError::Internal(e.to_string()))
|
|
}
|
|
|
|
pub fn branch_is_conflicted(&self) -> bool {
|
|
self.repo
|
|
.index()
|
|
.map(|idx| idx.has_conflicts())
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
pub fn branch_tracking_difference(&self, name: &str) -> GitResult<(usize, usize)> {
|
|
let upstream = self.branch_upstream(name)?;
|
|
match upstream {
|
|
Some(u) => self.branch_ahead_behind(name, &u.name),
|
|
None => Ok((0, 0)),
|
|
}
|
|
}
|
|
}
|