diff --git a/src/git/commit.rs b/src/git/commit.rs index 4bd74878..c1c1622f 100644 --- a/src/git/commit.rs +++ b/src/git/commit.rs @@ -70,6 +70,10 @@ impl Repository { let signature = if self.ssh_sign() { let program = self.ssh_program(); ssh_sign_string(program, key, &commit_as_str)? + } else if self.x509_sign() { + let program = self.gpg_x509_program(); + let user = sig.email().ok_or(Git2Error::MissingEmailInSignature)?; + x509_gitsign(program, user, &commit_as_str)? } else { let program = self.gpg_program(); gpg_sign_string(program, key, &commit_as_str)? @@ -86,6 +90,32 @@ impl Repository { } } +fn x509_gitsign(program: String, user: &str, content: &str) -> Result { + let mut child = Command::new(program); + child.args(["--armor", "-b", "-s", "-u", user]); + + let mut child = child + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .expect("error calling gpg command, is gpg installed ?"); + + { + let stdin = child.stdin.as_mut().unwrap(); + stdin.write_all(content.as_bytes())?; + } + + child.wait_with_output().map(|output| { + if output.status.success() { + Ok(String::from_utf8_lossy(&output.stdout).to_string()) + } else { + Err(Git2Error::GpgError( + String::from_utf8_lossy(&output.stderr).to_string(), + )) + } + })? +} + fn gpg_sign_string( program: String, key: Option, @@ -140,9 +170,9 @@ fn ssh_sign_string( let buffer_ref = buffer.into_temp_path(); child.args([ "-f", - signing_key_ref.to_str().unwrap(), - buffer_ref.to_str().unwrap(), - ]); // TODO: Is there a way to avoid unwrap here ? + signing_key_ref.to_string_lossy().as_ref(), + buffer_ref.to_string_lossy().as_ref(), + ]); child .stdin(Stdio::null()) diff --git a/src/git/error.rs b/src/git/error.rs index 8ce64224..f90fbb0d 100644 --- a/src/git/error.rs +++ b/src/git/error.rs @@ -29,6 +29,7 @@ pub enum Git2Error { InvalidCommitRangePattern(String), SshProgramError(String), SshError(String), + MissingEmailInSignature, UnknownRevision(String), } @@ -129,6 +130,9 @@ impl Display for Git2Error { Git2Error::InvalidCommitRangePattern(pattern) => { writeln!(f, "invalid commit range pattern: `{pattern}`") } + Git2Error::MissingEmailInSignature => { + writeln!(f, "`user.email` is required to sign commit with gitsign") + } Git2Error::SshProgramError(_) => { writeln!(f, "there was a problem while executing the ssh program") } diff --git a/src/git/repository.rs b/src/git/repository.rs index f75ac364..524331d6 100644 --- a/src/git/repository.rs +++ b/src/git/repository.rs @@ -19,6 +19,13 @@ impl Repository { config.get_bool("commit.gpgSign").unwrap_or(false) } + pub(crate) fn gpg_x509_program(&self) -> String { + let config = self.0.config().expect("failed to retrieve gitconfig"); + config + .get_string("gpg.x509.program") + .unwrap_or("gpg".to_string()) + } + pub(crate) fn gpg_program(&self) -> String { let config = self.0.config().expect("failed to retrieve gitconfig"); config @@ -44,6 +51,17 @@ impl Repository { .unwrap_or("ssh-keygen".to_string()) } + pub(crate) fn x509_sign(&self) -> bool { + let config = self.0.config().expect("failed to retrieve gitconfig"); + if config.get_bool("commit.gpgSign").is_err() { + return false; + } + + config + .get_string("gpg.format") + .is_ok_and(|s| s.to_lowercase() == "x509") + } + pub(crate) fn init + ?Sized>(path: &S) -> Result { let repository = Git2Repository::init(path).map_err(Git2Error::FailedToInitializeRepository)?;