use crate::commit::types::*; use crate::{GitDomain, GitError, GitResult}; impl GitDomain { pub fn commit_revert( &self, revert_oid: &CommitOid, author: &CommitSignature, committer: &CommitSignature, message: Option<&str>, mainline: u32, update_ref: Option<&str>, ) -> GitResult { let revert_commit = self .repo() .find_commit(revert_oid.to_oid()?) .map_err(|_e| GitError::ObjectNotFound(revert_oid.to_string()))?; let head_oid = self .repo() .head() .ok() .and_then(|r| r.target()) .ok_or_else(|| GitError::Internal("HEAD is not attached".to_string()))?; let our_commit = self .repo() .find_commit(head_oid) .map_err(|e| GitError::Internal(e.to_string()))?; let mut index = self .repo() .revert_commit(&revert_commit, &our_commit, mainline, None) .map_err(|e| GitError::Internal(e.to_string()))?; let tree_oid = index .write_tree_to(&*self.repo()) .map_err(|e| GitError::Internal(e.to_string()))?; let tree = self .repo() .find_tree(tree_oid) .map_err(|e| GitError::Internal(e.to_string()))?; let original_summary = revert_commit.summary().unwrap_or(""); let msg: String = match message { Some(m) => m.to_string(), None => format!("Revert \"{}\"", original_summary), }; let author = self.commit_signature_to_git2(author)?; let committer = self.commit_signature_to_git2(committer)?; // When mainline > 0, revert creates a merge commit with two parents: // (mainline_parent, our_commit). Otherwise single parent (our_commit). let oid = if mainline > 0 { let parent_count = revert_commit.parent_count(); let idx = (mainline - 1) as usize; if idx >= parent_count { return Err(GitError::Internal(format!( "mainline parent index {} out of range (commit has {} parents)", idx, parent_count ))); } let mainline_parent = revert_commit .parent(idx) .map_err(|e| GitError::Internal(e.to_string()))?; self.repo() .commit( update_ref, &author, &committer, &msg, &tree, &[&mainline_parent, &our_commit], ) .map_err(|e| GitError::Internal(e.to_string()))? } else { self.repo() .commit(update_ref, &author, &committer, &msg, &tree, &[&our_commit]) .map_err(|e| GitError::Internal(e.to_string()))? }; Ok(CommitOid::from_git2(oid)) } pub fn commit_revert_would_conflict( &self, revert_oid: &CommitOid, mainline: u32, ) -> GitResult { let revert_commit = self .repo() .find_commit(revert_oid.to_oid()?) .map_err(|_e| GitError::ObjectNotFound(revert_oid.to_string()))?; let head_oid = self .repo() .head() .ok() .and_then(|r| r.target()) .ok_or_else(|| GitError::Internal("HEAD is not attached".to_string()))?; let our_commit = self .repo() .find_commit(head_oid) .map_err(|e| GitError::Internal(e.to_string()))?; match self .repo() .revert_commit(&revert_commit, &our_commit, mainline, None) { Ok(index) => { let has_conflicts = (0..index.len()).any(|i| { index .get(i) .map(|e| (e.flags >> 12) & 0x3 > 0) .unwrap_or(false) }); Ok(has_conflicts) } Err(e) => { if e.code() == git2::ErrorCode::Conflict { Ok(true) } else { Err(GitError::Internal(e.to_string())) } } } } pub fn commit_revert_abort(&self, reset_type: &str) -> GitResult<()> { let kind = match reset_type { "soft" => git2::ResetType::Soft, "mixed" => git2::ResetType::Mixed, "hard" => git2::ResetType::Hard, _ => { return Err(GitError::Internal(format!( "unknown reset type: {}", reset_type ))); } }; 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, kind, None) .map_err(|e| GitError::Internal(e.to_string())) } }