Merge pull request #3969 from gitbutlerapp/handle-sign-failure

handle sign failure
This commit is contained in:
Kiril Videlov 2024-06-03 22:17:14 +02:00 committed by GitHub
commit 49af6f502a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 125 additions and 113 deletions

View File

@ -6,7 +6,8 @@ export enum Code {
Unknown = 'errors.unknown', Unknown = 'errors.unknown',
Validation = 'errors.validation', Validation = 'errors.validation',
ProjectsGitAuth = 'errors.projects.git.auth', ProjectsGitAuth = 'errors.projects.git.auth',
DefaultTargetNotFound = 'errors.projects.default_target.not_found' DefaultTargetNotFound = 'errors.projects.default_target.not_found',
CommitSigningFailed = 'errors.commit.signing_failed'
} }
export class UserError extends Error { export class UserError extends Error {

View File

@ -64,7 +64,20 @@ export class BranchController {
}); });
posthog.capture('Commit Successful'); posthog.capture('Commit Successful');
} catch (err: any) { } catch (err: any) {
if (err.code === 'errors.commit.signing_failed') {
showToast({
title: 'Failed to commit due to signing error',
message: `
You can disable commit signing in the project settings or review the signing setup within your git configuration.
Please check our [documentation](https://docs.gitbutler.com/features/virtual-branches/verifying-commits) on setting up commit signing and verification.
`,
errorMessage: err.message,
style: 'error'
});
} else {
showError('Failed to commit changes', err); showError('Failed to commit changes', err);
}
posthog.capture('Commit Failed', err); posthog.capture('Commit Failed', err);
throw err; throw err;
} }

View File

@ -2,7 +2,7 @@ use crate::projects::Project;
use anyhow::Result; use anyhow::Result;
use git2::ConfigLevel; use git2::ConfigLevel;
const CFG_SIGN_COMMITS: &str = "gitbutler.signCommits"; use super::CFG_SIGN_COMMITS;
impl Project { impl Project {
pub fn set_sign_commits(&self, val: bool) -> Result<()> { pub fn set_sign_commits(&self, val: bool) -> Result<()> {

View File

@ -1 +1,3 @@
pub mod git; pub mod git;
pub const CFG_SIGN_COMMITS: &str = "gitbutler.signCommits";

View File

@ -131,6 +131,7 @@ pub enum Code {
Validation, Validation,
ProjectGitAuth, ProjectGitAuth,
DefaultTargetNotFound, DefaultTargetNotFound,
CommitSigningFailed,
} }
impl std::fmt::Display for Code { impl std::fmt::Display for Code {
@ -140,6 +141,7 @@ impl std::fmt::Display for Code {
Code::Validation => "errors.validation", Code::Validation => "errors.validation",
Code::ProjectGitAuth => "errors.projects.git.auth", Code::ProjectGitAuth => "errors.projects.git.auth",
Code::DefaultTargetNotFound => "errors.projects.default_target.not_found", Code::DefaultTargetNotFound => "errors.projects.default_target.not_found",
Code::CommitSigningFailed => "errors.commit.signing_failed",
}; };
f.write_str(code) f.write_str(code)
} }

View File

@ -1,8 +1,10 @@
use anyhow::{bail, Context, Result}; use anyhow::{anyhow, bail, Context, Result};
use git2::{BlameOptions, Repository, Tree}; use git2::{BlameOptions, Repository, Tree};
use std::{path::Path, process::Stdio, str}; use std::{path::Path, process::Stdio, str};
use tracing::instrument; use tracing::instrument;
use crate::{config::CFG_SIGN_COMMITS, error::Code};
use super::Refname; use super::Refname;
use std::io::Write; use std::io::Write;
#[cfg(unix)] #[cfg(unix)]
@ -79,8 +81,16 @@ impl RepositoryExt for Repository {
let commit_buffer = inject_change_id(&commit_buffer, change_id)?; let commit_buffer = inject_change_id(&commit_buffer, change_id)?;
let oid = do_commit_buffer(self, commit_buffer)?; let should_sign = self.config()?.get_bool(CFG_SIGN_COMMITS).unwrap_or(false);
let oid = if should_sign {
let signature = sign_buffer(self, &commit_buffer)
.map_err(|e| anyhow!(e).context(Code::CommitSigningFailed))?;
self.commit_signed(&commit_buffer, &signature, None)?
} else {
self.odb()?
.write(git2::ObjectType::Commit, commit_buffer.as_bytes())?
};
// update reference // update reference
if let Some(refname) = update_ref { if let Some(refname) = update_ref {
self.reference(&refname.to_string(), oid, true, message)?; self.reference(&refname.to_string(), oid, true, message)?;
@ -106,13 +116,9 @@ impl RepositoryExt for Repository {
} }
} }
/// takes raw commit data and commits it to the repository /// Signs the buffer with the configured gpg key, returning the signature.
/// - if the git config commit.gpgSign is set, it will sign the commit fn sign_buffer(repo: &git2::Repository, buffer: &String) -> Result<String> {
/// returns an oid of the new commit object
fn do_commit_buffer(repo: &git2::Repository, buffer: String) -> Result<git2::Oid> {
// check git config for gpg.signingkey // check git config for gpg.signingkey
let should_sign = repo.config()?.get_bool("commit.gpgSign").unwrap_or(false);
if should_sign {
// TODO: support gpg.ssh.defaultKeyCommand to get the signing key if this value doesn't exist // TODO: support gpg.ssh.defaultKeyCommand to get the signing key if this value doesn't exist
let signing_key = repo.config()?.get_string("user.signingkey"); let signing_key = repo.config()?.get_string("user.signingkey");
if let Ok(signing_key) = signing_key { if let Ok(signing_key) = signing_key {
@ -177,12 +183,8 @@ fn do_commit_buffer(repo: &git2::Repository, buffer: String) -> Result<git2::Oid
// read signed_storage path plus .sig // read signed_storage path plus .sig
let signature_path = buffer_file_to_sign_path.with_extension("sig"); let signature_path = buffer_file_to_sign_path.with_extension("sig");
let sig_data = std::fs::read(signature_path)?; let sig_data = std::fs::read(signature_path)?;
let signature = String::from_utf8_lossy(&sig_data); let signature = String::from_utf8_lossy(&sig_data).into_owned();
let oid = repo return Ok(signature);
.commit_signed(&buffer, &signature, None)
.map(Into::into)
.map_err(Into::into);
return oid;
} }
} else { } else {
// is gpg // is gpg
@ -197,7 +199,9 @@ fn do_commit_buffer(repo: &git2::Repository, buffer: String) -> Result<git2::Oid
#[cfg(windows)] #[cfg(windows)]
cmd.creation_flags(0x08000000); // CREATE_NO_WINDOW cmd.creation_flags(0x08000000); // CREATE_NO_WINDOW
let mut child = cmd.spawn()?; let mut child = cmd
.spawn()
.context(anyhow::format_err!("failed to spawn {:?}", cmd))?;
child child
.stdin .stdin
.take() .take()
@ -207,22 +211,12 @@ fn do_commit_buffer(repo: &git2::Repository, buffer: String) -> Result<git2::Oid
let output = child.wait_with_output()?; let output = child.wait_with_output()?;
if output.status.success() { if output.status.success() {
// read stdout // read stdout
let signature = String::from_utf8_lossy(&output.stdout); let signature = String::from_utf8_lossy(&output.stdout).into_owned();
let oid = repo return Ok(signature);
.commit_signed(&buffer, &signature, None)
.map(Into::into)
.map_err(Into::into);
return oid;
} }
} }
} }
} Err(anyhow::anyhow!("Unsupported commit signing method"))
let oid = repo
.odb()?
.write(git2::ObjectType::Commit, buffer.as_bytes())?;
Ok(oid)
} }
fn is_literal_ssh_key(string: &str) -> (bool, &str) { fn is_literal_ssh_key(string: &str) -> (bool, &str) {
@ -244,7 +238,7 @@ fn inject_change_id(commit_buffer: &[u8], change_id: Option<&str>) -> Result<Str
.unwrap_or_else(|| format!("{}", uuid::Uuid::new_v4())); .unwrap_or_else(|| format!("{}", uuid::Uuid::new_v4()));
let commit_ends_in_newline = commit_buffer.ends_with(b"\n"); let commit_ends_in_newline = commit_buffer.ends_with(b"\n");
let commit_buffer = str::from_utf8(commit_buffer).unwrap(); let commit_buffer = str::from_utf8(commit_buffer)?;
let lines = commit_buffer.lines(); let lines = commit_buffer.lines();
let mut new_buffer = String::new(); let mut new_buffer = String::new();
let mut found = false; let mut found = false;