diff --git a/Cargo.lock b/Cargo.lock index efbd79c453..4124fc0536 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,6 +63,8 @@ version = "0.9.0" dependencies = [ "crossbeam-channel", "git2", + "gpg-error", + "gpgme", "invalidstring", "log", "rayon-core", @@ -219,6 +221,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "conv" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" +dependencies = [ + "custom_derive", +] + [[package]] name = "cpp_demangle" version = "0.3.0" @@ -312,6 +323,22 @@ dependencies = [ "winapi", ] +[[package]] +name = "cstr-argument" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20bd4e8067c20c7c3a4dea759ef91d4b18418ddb5bd8837ef6e2f2f93ca7ccbb" +dependencies = [ + "cfg-if", + "memchr", +] + +[[package]] +name = "custom_derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" + [[package]] name = "debugid" version = "0.7.2" @@ -432,6 +459,42 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "gpg-error" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1eafd8f60a0e9112caa9057f9a5fca2c3b17a9b6f3c035e79c35af9b94905137" +dependencies = [ + "libgpg-error-sys", +] + +[[package]] +name = "gpgme" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528b8b981855eb0c74696a408919b1bf5dbe7f4cd99261bb1b47097d9bf3478f" +dependencies = [ + "bitflags", + "conv", + "cstr-argument", + "gpg-error", + "gpgme-sys", + "libc", + "once_cell", + "smallvec", + "static_assertions", +] + +[[package]] +name = "gpgme-sys" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b293be40a67cf00d4cdbefc8bc87c10d2b9581fcb9ac226084c53a556f2e31f" +dependencies = [ + "libc", + "libgpg-error-sys", +] + [[package]] name = "hermit-abi" version = "0.1.15" @@ -536,6 +599,15 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libgpg-error-sys" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a1088124ffd50d800fa4daa9854a1ce6fbaa4b9cc953ee571c6b3cc27e5928f" +dependencies = [ + "libc", +] + [[package]] name = "libz-sys" version = "1.0.25" @@ -727,6 +799,12 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" +[[package]] +name = "once_cell" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" + [[package]] name = "parking_lot" version = "0.10.2" @@ -1052,6 +1130,12 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "str_stack" version = "0.1.0" diff --git a/asyncgit/Cargo.toml b/asyncgit/Cargo.toml index 63be7ff293..ed84abfd87 100644 --- a/asyncgit/Cargo.toml +++ b/asyncgit/Cargo.toml @@ -18,6 +18,8 @@ rayon-core = "1.7" crossbeam-channel = "0.4" log = "0.4" thiserror = "1.0" +gpg-error = "0.5.1" +gpgme = "0.9.2" [dev-dependencies] tempfile = "3.1" diff --git a/asyncgit/src/error.rs b/asyncgit/src/error.rs index f00f587b25..eab15fdb3f 100644 --- a/asyncgit/src/error.rs +++ b/asyncgit/src/error.rs @@ -13,6 +13,9 @@ pub enum Error { #[error("git error:{0}")] Git(#[from] git2::Error), + + #[error("gpg error:#{0}")] + Gpg(#[from] gpg_error::Error), } pub type Result = std::result::Result; diff --git a/asyncgit/src/sync/commit.rs b/asyncgit/src/sync/commit.rs index b24e7ce5ed..a9c8f0a0f1 100644 --- a/asyncgit/src/sync/commit.rs +++ b/asyncgit/src/sync/commit.rs @@ -1,6 +1,7 @@ use super::{get_head, utils::repo, CommitId}; use crate::error::Result; use git2::{ErrorCode, ObjectType, Repository, Signature}; +use gpgme::{Context, Protocol}; use scopetime::scope_time; /// @@ -54,6 +55,7 @@ pub fn commit(repo_path: &str, msg: &str) -> Result { scope_time!("commit"); let repo = repo(repo_path)?; + let config = repo.config()?; let signature = signature_allow_undefined_name(&repo)?; let mut index = repo.index()?; @@ -68,8 +70,54 @@ pub fn commit(repo_path: &str, msg: &str) -> Result { let parents = parents.iter().collect::>(); - Ok(repo - .commit( + let commit_oid = if config.get_bool("commit.gpgsign")? { + // Generate commit content + let commit_bufffer = repo.commit_create_buffer( + &signature, + &signature, + msg, + &tree, + parents.as_slice(), + )?; + let commit_content = + commit_bufffer.as_str().expect("Buffer was not valid UTF-8"); + + // Prepare to sign using the designated key in the user's git config + let mut gpg_ctx = Context::from_protocol(Protocol::OpenPgp)?; + let key = gpg_ctx.get_key(config.get_string("user.signingkey")?)?; + gpg_ctx.add_signer(&key)?; + gpg_ctx.set_armor(true); + + // Create GPG signature for commit content + let mut signature_buffer = Vec::new(); + gpg_ctx.sign_detached(&*commit_bufffer, &mut signature_buffer)?; + let gpg_signature = std::str::from_utf8(&signature_buffer).unwrap(); + + let commit_oid = + repo.commit_signed(&commit_content, &gpg_signature, None)?; + + match repo.head() { + // If HEAD reference is returned, simply update the target. + Ok(mut head) => { head.set_target(commit_oid, msg)?; } + // If there is an error getting HEAD, likely it is a new repo + // and a reference to a default branch needs to be created. + Err(_) => { + // Default branch name behavior as of git 2.28. + let default_branch_name = + config.get_str("init.defaultBranch").unwrap_or("master"); + + repo.reference( + &format!("refs/heads/{}", default_branch_name), + commit_oid, + true, + msg + )?; + } + } + + commit_oid + } else { + repo.commit( Some("HEAD"), &signature, &signature, @@ -77,7 +125,9 @@ pub fn commit(repo_path: &str, msg: &str) -> Result { &tree, parents.as_slice(), )? - .into()) + }; + + Ok(commit_oid.into()) } /// Tag a commit.