From 32b1dd54a3be43f98be1e7243344c70c97d2867e Mon Sep 17 00:00:00 2001 From: Grant Bourque Date: Wed, 29 Jul 2020 17:45:58 -0500 Subject: [PATCH 1/4] WIP: Add support for GPG signing commits --- Cargo.lock | 84 +++++++++++++++++++++++++++++++++++++ asyncgit/Cargo.toml | 2 + asyncgit/src/error.rs | 3 ++ asyncgit/src/sync/commit.rs | 66 +++++++++++++++++++++++++++-- 4 files changed, 152 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 52a1846123..e1aa8984a5 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..e9614097a4 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,64 @@ 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 +135,9 @@ pub fn commit(repo_path: &str, msg: &str) -> Result { &tree, parents.as_slice(), )? - .into()) + }; + + Ok(commit_oid.into()) } /// Tag a commit. From 8fa591a79fd215564da52f891af95f1297f3a8bd Mon Sep 17 00:00:00 2001 From: Grant Bourque Date: Wed, 29 Jul 2020 20:18:08 -0500 Subject: [PATCH 2/4] WIP: Add GnuPG dependencies to CI --- .github/workflows/ci.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 09b579d4b2..36589544fc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,14 @@ jobs: profile: minimal components: clippy + - name: Install GnuPG on Ubuntu + if: matrix.os == 'ubuntu-latest' + run: sudo apt-get -qq install libgpg-error-dev libgpgme11-dev + + - name: Install GnuPG on MacOS + if: matrix.os == 'macos-latest' + run: brew install gpgme + - name: Build Debug run: | rustc --version @@ -58,6 +66,9 @@ jobs: profile: minimal target: x86_64-unknown-linux-musl + - name: Install GnuPG + run: sudo apt-get -qq install libgpg-error-dev libgpgme11-dev + - name: Setup MUSL run: | sudo apt-get -qq install musl-tools From 3d433012ed97559bf3f888b44286cf1a07eb765d Mon Sep 17 00:00:00 2001 From: Grant Bourque Date: Wed, 29 Jul 2020 21:27:17 -0500 Subject: [PATCH 3/4] Remove reliance from presence of config options --- asyncgit/src/sync/commit.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/asyncgit/src/sync/commit.rs b/asyncgit/src/sync/commit.rs index e9614097a4..cb9e7761bd 100644 --- a/asyncgit/src/sync/commit.rs +++ b/asyncgit/src/sync/commit.rs @@ -70,7 +70,10 @@ pub fn commit(repo_path: &str, msg: &str) -> Result { let parents = parents.iter().collect::>(); - let commit_oid = if config.get_bool("commit.gpgsign")? { + let commit_oid = if config + .get_bool("commit.gpgsign") + .unwrap_or(false) + { // Generate commit content let commit_bufffer = repo.commit_create_buffer( &signature, @@ -85,17 +88,18 @@ pub fn commit(repo_path: &str, msg: &str) -> Result { // 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)?; + if let Ok(key_id) = config.get_string("user.signingkey") { + let key = gpg_ctx.get_key(key_id)?; + 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 gpg_signature = std::str::from_utf8(&signature_buffer) + .expect("Buffer was not valid UTF-8"); let commit_oid = repo.commit_signed( &commit_content, From 463bc91072cb3752ba781d506c9ec339a820f717 Mon Sep 17 00:00:00 2001 From: Grant Bourque Date: Thu, 30 Jul 2020 12:39:53 -0500 Subject: [PATCH 4/4] WIP: Figure out Windows install --- .github/workflows/ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36589544fc..e875ba13c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,6 +39,15 @@ jobs: if: matrix.os == 'macos-latest' run: brew install gpgme + - name: Install GnuPG on Windows + if: matrix.os == 'windows-latest' + run: | + $path = [Environment]::GetEnvironmentVariable("path", "machine") + $newPath = ($path.Split(';') | Where-Object { $_ -eq 'C:\ProgramData\chocolatey\bin' }) -join ';' + [Environment]::SetEnvironmentVariable("path", $newPath, "machine") + choco install gpg4win + refreshenv + - name: Build Debug run: | rustc --version