//! Commit creation and modification operations. use git2::Signature; use crate::commit::types::*; use crate::{GitDomain, GitError, GitResult}; fn parents_to_git2<'a>( repo: &'a git2::Repository, parent_ids: &[CommitOid], ) -> GitResult>> { parent_ids .iter() .map(|oid| { repo.find_commit(oid.to_oid()?) .map_err(|_e| GitError::ObjectNotFound(oid.to_string())) }) .collect() } impl GitDomain { pub fn commit_default_signature(&self) -> GitResult { let sig = self .repo() .signature() .map_err(|e| GitError::Internal(e.to_string()))?; Ok(CommitSignature::from_git2(sig)) } pub fn commit_signature_now(&self, name: &str, email: &str) -> GitResult { let sig = Signature::now(name, email).map_err(|e| GitError::Internal(e.to_string()))?; Ok(CommitSignature::from_git2(sig)) } pub fn commit_signature_at( &self, name: &str, email: &str, time_secs: i64, offset_minutes: i32, ) -> GitResult { let time = git2::Time::new(time_secs, offset_minutes); let sig = Signature::new(name, email, &time).map_err(|e| GitError::Internal(e.to_string()))?; Ok(CommitSignature::from_git2(sig)) } pub fn commit_signature_to_git2(&self, sig: &CommitSignature) -> GitResult> { let time = git2::Time::new(sig.time_secs, sig.offset_minutes); Signature::new(&sig.name, &sig.email, &time).map_err(|e| GitError::Internal(e.to_string())) } pub fn commit_create( &self, update_ref: Option<&str>, author: &CommitSignature, committer: &CommitSignature, message: &str, tree_id: &CommitOid, parent_ids: &[CommitOid], ) -> GitResult { let author = self.commit_signature_to_git2(author)?; let committer = self.commit_signature_to_git2(committer)?; let tree = self .repo() .find_tree(tree_id.to_oid()?) .map_err(|e| GitError::Internal(e.to_string()))?; let parents = parents_to_git2(&*self.repo(), parent_ids)?; let parent_refs: Vec<&git2::Commit<'_>> = parents.iter().map(|p| p as &git2::Commit).collect(); let oid = self .repo() .commit( update_ref, &author, &committer, message, &tree, &parent_refs, ) .map_err(|e| GitError::Internal(e.to_string()))?; Ok(CommitOid::from_git2(oid)) } pub fn commit_create_from_index( &self, update_ref: Option<&str>, author: &CommitSignature, committer: &CommitSignature, message: &str, parent_ids: &[CommitOid], ) -> GitResult { let author = self.commit_signature_to_git2(author)?; let committer = self.commit_signature_to_git2(committer)?; let mut index = self .repo() .index() .map_err(|e| GitError::Internal(e.to_string()))?; let tree_oid = index .write_tree() .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 parents = parents_to_git2(&*self.repo(), parent_ids)?; let parent_refs: Vec<&git2::Commit<'_>> = parents.iter().map(|p| p as &git2::Commit).collect(); let oid = self .repo() .commit( update_ref, &author, &committer, message, &tree, &parent_refs, ) .map_err(|e| GitError::Internal(e.to_string()))?; Ok(CommitOid::from_git2(oid)) } pub fn commit_sign( &self, author: &CommitSignature, committer: &CommitSignature, message: &str, tree_id: &CommitOid, parent_ids: &[CommitOid], gpg_signature: &str, signature_field: Option<&str>, ) -> GitResult { let author = self.commit_signature_to_git2(author)?; let committer = self.commit_signature_to_git2(committer)?; let tree = self .repo() .find_tree(tree_id.to_oid()?) .map_err(|e| GitError::Internal(e.to_string()))?; let parents = parents_to_git2(&*self.repo(), parent_ids)?; let parent_refs: Vec<&git2::Commit<'_>> = parents.iter().map(|p| p as &git2::Commit).collect(); let buf = self .repo() .commit_create_buffer(&author, &committer, message, &tree, &parent_refs) .map_err(|e| GitError::Internal(e.to_string()))?; let buf_str = std::str::from_utf8(&*buf).map_err(|e| GitError::Internal(e.to_string()))?; let oid = self .repo() .commit_signed(buf_str, gpg_signature, signature_field) .map_err(|e| GitError::Internal(e.to_string()))?; Ok(CommitOid::from_git2(oid)) } pub fn commit_extract_signature( &self, commit_oid: &CommitOid, signature_field: Option<&str>, ) -> GitResult> { match self .repo() .extract_signature(&commit_oid.to_oid()?, signature_field) { Ok((sig_buf, content_buf)) => Ok(Some(( String::from_utf8_lossy(&*sig_buf).to_string(), String::from_utf8_lossy(&*content_buf).to_string(), ))), Err(e) => { if e.code() == git2::ErrorCode::NotFound { Ok(None) } else { Err(GitError::Internal(e.to_string())) } } } } pub fn commit_empty( &self, update_ref: Option<&str>, author: &CommitSignature, committer: &CommitSignature, message: &str, parent_ids: &[CommitOid], ) -> GitResult { let author = self.commit_signature_to_git2(author)?; let committer = self.commit_signature_to_git2(committer)?; let tree = if let Some(first) = parent_ids.first() { let commit = self .repo() .find_commit(first.to_oid()?) .map_err(|_e| GitError::ObjectNotFound(first.to_string()))?; commit .tree() .map_err(|e| GitError::Internal(e.to_string()))? } else { let mut index = self .repo() .index() .map_err(|e| GitError::Internal(e.to_string()))?; let tree_oid = index .write_tree() .map_err(|e| GitError::Internal(e.to_string()))?; self.repo() .find_tree(tree_oid) .map_err(|e| GitError::Internal(e.to_string()))? }; let parents = parents_to_git2(&*self.repo(), parent_ids)?; let parent_refs: Vec<&git2::Commit<'_>> = parents.iter().map(|p| p as &git2::Commit).collect(); let oid = self .repo() .commit( update_ref, &author, &committer, message, &tree, &parent_refs, ) .map_err(|e| GitError::Internal(e.to_string()))?; Ok(CommitOid::from_git2(oid)) } pub fn commit_amend( &self, commit_oid: &CommitOid, update_ref: Option<&str>, author: Option<&CommitSignature>, committer: Option<&CommitSignature>, message_encoding: Option<&str>, message: Option<&str>, tree_id: Option<&CommitOid>, ) -> GitResult { let commit = self .repo() .find_commit(commit_oid.to_oid()?) .map_err(|_e| GitError::ObjectNotFound(commit_oid.to_string()))?; let author = author .map(|a| self.commit_signature_to_git2(a)) .transpose()?; let committer = committer .map(|c| self.commit_signature_to_git2(c)) .transpose()?; let tree = tree_id .map(|t| { self.repo .find_tree(t.to_oid()?) .map_err(|e| GitError::Internal(e.to_string())) }) .transpose()?; let oid = commit .amend( update_ref, author.as_ref(), committer.as_ref(), message_encoding, message, tree.as_ref(), ) .map_err(|e| GitError::Internal(e.to_string()))?; Ok(CommitOid::from_git2(oid)) } pub fn commit_amend_author( &self, commit_oid: &CommitOid, new_author: &CommitSignature, update_ref: Option<&str>, ) -> GitResult { self.commit_amend( commit_oid, update_ref, Some(new_author), None, None, None, None, ) } pub fn commit_amend_message( &self, commit_oid: &CommitOid, new_message: &str, update_ref: Option<&str>, ) -> GitResult { self.commit_amend( commit_oid, update_ref, None, None, None, Some(new_message), None, ) } pub fn commit_amend_tree( &self, commit_oid: &CommitOid, new_tree_id: &CommitOid, update_ref: Option<&str>, ) -> GitResult { self.commit_amend( commit_oid, update_ref, None, None, None, None, Some(new_tree_id), ) } }