gitdataai/libs/git/commit/create.rs
2026-04-15 09:08:09 +08:00

315 lines
9.9 KiB
Rust

//! 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<Vec<git2::Commit<'a>>> {
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<CommitSignature> {
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<CommitSignature> {
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<CommitSignature> {
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<Signature<'static>> {
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<CommitOid> {
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<CommitOid> {
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<CommitOid> {
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<Option<(String, String)>> {
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<CommitOid> {
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<CommitOid> {
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<CommitOid> {
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<CommitOid> {
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<CommitOid> {
self.commit_amend(
commit_oid,
update_ref,
None,
None,
None,
None,
Some(new_tree_id),
)
}
}