use super::{Commit, CommitError, CommitFields, CommitHeader, Signature};
use crate::ObjectId;
#[derive(Clone, Debug)]
pub struct CommitBuilder {
fields: CommitFields,
}
impl CommitBuilder {
pub fn new(tree: ObjectId, author: Signature, message: impl AsRef<[u8]>) -> Self {
Self {
fields: CommitFields {
tree,
parents: Vec::new(),
committer: author.clone(),
author,
extra_headers: Vec::new(),
message: message.as_ref().to_vec(),
},
}
}
pub fn from_config(
tree: ObjectId,
message: impl AsRef<[u8]>,
config: &crate::Config,
environment: &crate::workflow::Environment,
) -> Result<Self, CommitError> {
let author = Signature::author(config, environment)?;
let committer = Signature::committer(config, environment)?;
Ok(Self::new(tree, author, message).committer(committer))
}
pub fn parent(mut self, parent: ObjectId) -> Self {
self.fields.parents.push(parent);
self
}
pub fn parents(mut self, parents: impl IntoIterator<Item = ObjectId>) -> Self {
self.fields.parents.extend(parents);
self
}
pub fn committer(mut self, committer: Signature) -> Self {
self.fields.committer = committer;
self
}
pub fn header(mut self, name: impl AsRef<[u8]>, value: impl AsRef<[u8]>) -> Self {
self.fields.extra_headers.push(CommitHeader {
name: name.as_ref().to_vec(),
value: value.as_ref().to_vec(),
});
self
}
pub fn build(self) -> Result<Commit, CommitError> {
Commit::new(self.fields)
}
pub fn build_signed<E>(
mut self,
sign: impl FnOnce(&[u8]) -> Result<Vec<u8>, E>,
) -> Result<Commit, SignError<E>> {
if self
.fields
.extra_headers
.iter()
.any(|header| matches!(header.name.as_slice(), b"gpgsig" | b"gpgsig-sha256"))
{
return Err(SignError::ExistingSignature);
}
let unsigned = Commit::new(self.fields.clone()).map_err(SignError::Commit)?;
let signature = sign(unsigned.as_bytes()).map_err(SignError::Signer)?;
let name: &[u8] = match self.fields.tree.format() {
crate::ObjectFormat::Sha1 => b"gpgsig",
crate::ObjectFormat::Sha256 => b"gpgsig-sha256",
};
self.fields.extra_headers.push(CommitHeader {
name: name.to_vec(),
value: signature.strip_suffix(b"\n").unwrap_or(&signature).to_vec(),
});
Commit::new(self.fields).map_err(SignError::Commit)
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum SignError<E> {
#[error(transparent)]
Commit(CommitError),
#[error("signing commit")]
Signer(#[source] E),
#[error("commit already contains a signature header")]
ExistingSignature,
}