From e1d76d94bfc157fe481fe61c313acc47c3fa075c Mon Sep 17 00:00:00 2001 From: Matt Hammerly Date: Mon, 21 Apr 2025 11:09:35 -0700 Subject: [PATCH 1/6] breaking: update msrv to 1.80 --- .github/workflows/linux-git-devel.yml | 2 +- .github/workflows/linux.yml | 2 +- .github/workflows/macos.yml | 2 +- .github/workflows/windows.yml | 2 +- CHANGELOG.md | 1 + flake.lock | 6 +++--- git-branchless-hook/src/lib.rs | 2 +- git-branchless-init/src/lib.rs | 2 +- git-branchless-invoke/src/lib.rs | 2 +- git-branchless-lib/Cargo.toml | 2 +- git-branchless-lib/src/lib.rs | 2 +- git-branchless-move/src/lib.rs | 2 +- git-branchless-navigation/src/lib.rs | 2 +- git-branchless-opts/src/lib.rs | 2 +- git-branchless-record/src/lib.rs | 2 +- git-branchless-revset/src/lib.rs | 2 +- git-branchless-reword/src/lib.rs | 2 +- git-branchless-smartlog/src/lib.rs | 2 +- git-branchless-submit/src/lib.rs | 2 +- git-branchless-test/src/lib.rs | 2 +- git-branchless-undo/src/lib.rs | 2 +- git-branchless/Cargo.toml | 2 +- git-branchless/src/lib.rs | 2 +- rust-toolchain.toml | 2 +- scm-bisect/src/lib.rs | 2 +- 25 files changed, 27 insertions(+), 26 deletions(-) diff --git a/.github/workflows/linux-git-devel.yml b/.github/workflows/linux-git-devel.yml index 086c7175a..1a27ac3e7 100644 --- a/.github/workflows/linux-git-devel.yml +++ b/.github/workflows/linux-git-devel.yml @@ -54,7 +54,7 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.74 + toolchain: "1.80" override: true - uses: actions/checkout@v4 diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 4650c6d26..514226d15 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -74,7 +74,7 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.74 + toolchain: "1.80" override: true - name: Cache dependencies diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index c9bee8252..02001cd68 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -27,7 +27,7 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.74 + toolchain: "1.80" override: true - name: Cache dependencies diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 414d75c57..d42aeeb3e 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -20,7 +20,7 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.74 + toolchain: "1.80" override: true - name: Cache dependencies diff --git a/CHANGELOG.md b/CHANGELOG.md index 1739ff5a5..82a5838c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `scm-record` upgraded to [v0.5.0](https://github.com/arxanas/scm-record/releases/tag/v0.5.0). - (#1463): `git switch` now accepts a revset whose sole head will be checked out +- BREAKING: The minimum supported Rust version (MSRV) is now 1.80. ## [v0.10.0] - 2024-10-10 diff --git a/flake.lock b/flake.lock index a0bc47e3d..578c861af 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1708373374, - "narHash": "sha256-yEyDvDj/YQc4GOZa/cXx6YUzexD8uv1rUvD6SJVr6UI=", + "lastModified": 1744868846, + "narHash": "sha256-5RJTdUHDmj12Qsv7XOhuospjAjATNiTMElplWnJE9Hs=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "93e1c2d08467d1117ebac45689469613a9fe8453", + "rev": "ebe4301cbd8f81c4f8d3244b3632338bbeb6d49c", "type": "github" }, "original": { diff --git a/git-branchless-hook/src/lib.rs b/git-branchless-hook/src/lib.rs index f4c8348a0..d339baae6 100644 --- a/git-branchless-hook/src/lib.rs +++ b/git-branchless-hook/src/lib.rs @@ -14,7 +14,7 @@ clippy::clone_on_ref_ptr, clippy::dbg_macro )] -#![allow(clippy::too_many_arguments, clippy::blocks_in_if_conditions)] +#![allow(clippy::too_many_arguments, clippy::blocks_in_conditions)] use std::fmt::Write; use std::fs::File; diff --git a/git-branchless-init/src/lib.rs b/git-branchless-init/src/lib.rs index 5097e6895..4d65af814 100644 --- a/git-branchless-init/src/lib.rs +++ b/git-branchless-init/src/lib.rs @@ -7,7 +7,7 @@ clippy::clone_on_ref_ptr, clippy::dbg_macro )] -#![allow(clippy::too_many_arguments, clippy::blocks_in_if_conditions)] +#![allow(clippy::too_many_arguments, clippy::blocks_in_conditions)] use std::fmt::Write; use std::io::{stdin, stdout, BufRead, BufReader, Write as WriteIo}; diff --git a/git-branchless-invoke/src/lib.rs b/git-branchless-invoke/src/lib.rs index a6cd9738f..d56315bb3 100644 --- a/git-branchless-invoke/src/lib.rs +++ b/git-branchless-invoke/src/lib.rs @@ -11,7 +11,7 @@ clippy::clone_on_ref_ptr, clippy::dbg_macro )] -#![allow(clippy::too_many_arguments, clippy::blocks_in_if_conditions)] +#![allow(clippy::too_many_arguments, clippy::blocks_in_conditions)] use std::any::Any; use std::collections::HashMap; diff --git a/git-branchless-lib/Cargo.toml b/git-branchless-lib/Cargo.toml index 15d8ee590..494a1b72e 100644 --- a/git-branchless-lib/Cargo.toml +++ b/git-branchless-lib/Cargo.toml @@ -6,7 +6,7 @@ keywords = ["git"] license = "MIT OR Apache-2.0" name = "git-branchless-lib" repository = "https://github.com/arxanas/git-branchless" -rust-version = "1.64.0" +rust-version = "1.80" version = "0.10.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/git-branchless-lib/src/lib.rs b/git-branchless-lib/src/lib.rs index dfaeffbea..04edaac69 100644 --- a/git-branchless-lib/src/lib.rs +++ b/git-branchless-lib/src/lib.rs @@ -7,7 +7,7 @@ clippy::clone_on_ref_ptr, clippy::dbg_macro )] -#![allow(clippy::too_many_arguments, clippy::blocks_in_if_conditions)] +#![allow(clippy::too_many_arguments, clippy::blocks_in_conditions)] pub mod core; pub mod git; diff --git a/git-branchless-move/src/lib.rs b/git-branchless-move/src/lib.rs index 86b081dea..78b175b1d 100644 --- a/git-branchless-move/src/lib.rs +++ b/git-branchless-move/src/lib.rs @@ -10,7 +10,7 @@ clippy::clone_on_ref_ptr, clippy::dbg_macro )] -#![allow(clippy::too_many_arguments, clippy::blocks_in_if_conditions)] +#![allow(clippy::too_many_arguments, clippy::blocks_in_conditions)] use std::collections::HashMap; use std::fmt::Write; diff --git a/git-branchless-navigation/src/lib.rs b/git-branchless-navigation/src/lib.rs index 729a3603d..a0dd606de 100644 --- a/git-branchless-navigation/src/lib.rs +++ b/git-branchless-navigation/src/lib.rs @@ -7,7 +7,7 @@ clippy::clone_on_ref_ptr, clippy::dbg_macro )] -#![allow(clippy::too_many_arguments, clippy::blocks_in_if_conditions)] +#![allow(clippy::too_many_arguments, clippy::blocks_in_conditions)] pub mod prompt; diff --git a/git-branchless-opts/src/lib.rs b/git-branchless-opts/src/lib.rs index 702ec1cbc..37d507dfb 100644 --- a/git-branchless-opts/src/lib.rs +++ b/git-branchless-opts/src/lib.rs @@ -7,7 +7,7 @@ clippy::clone_on_ref_ptr, clippy::dbg_macro )] -#![allow(clippy::too_many_arguments, clippy::blocks_in_if_conditions)] +#![allow(clippy::too_many_arguments, clippy::blocks_in_conditions)] // These URLs are printed verbatim in help output, so we don't want to add extraneous Markdown // formatting. #![allow(rustdoc::bare_urls)] diff --git a/git-branchless-record/src/lib.rs b/git-branchless-record/src/lib.rs index 166f6539b..2281f6c3c 100644 --- a/git-branchless-record/src/lib.rs +++ b/git-branchless-record/src/lib.rs @@ -7,7 +7,7 @@ clippy::clone_on_ref_ptr, clippy::dbg_macro )] -#![allow(clippy::too_many_arguments, clippy::blocks_in_if_conditions)] +#![allow(clippy::too_many_arguments, clippy::blocks_in_conditions)] use std::collections::HashSet; use std::ffi::OsString; diff --git a/git-branchless-revset/src/lib.rs b/git-branchless-revset/src/lib.rs index 14efa735a..0284a93a5 100644 --- a/git-branchless-revset/src/lib.rs +++ b/git-branchless-revset/src/lib.rs @@ -8,7 +8,7 @@ clippy::clone_on_ref_ptr, clippy::dbg_macro )] -#![allow(clippy::too_many_arguments, clippy::blocks_in_if_conditions)] +#![allow(clippy::too_many_arguments, clippy::blocks_in_conditions)] mod ast; mod builtins; diff --git a/git-branchless-reword/src/lib.rs b/git-branchless-reword/src/lib.rs index ef53ad4d3..4f003381a 100644 --- a/git-branchless-reword/src/lib.rs +++ b/git-branchless-reword/src/lib.rs @@ -7,7 +7,7 @@ clippy::clone_on_ref_ptr, clippy::dbg_macro )] -#![allow(clippy::too_many_arguments, clippy::blocks_in_if_conditions)] +#![allow(clippy::too_many_arguments, clippy::blocks_in_conditions)] pub mod dialoguer_edit; diff --git a/git-branchless-smartlog/src/lib.rs b/git-branchless-smartlog/src/lib.rs index e3b3bdc3f..43ccc2bd0 100644 --- a/git-branchless-smartlog/src/lib.rs +++ b/git-branchless-smartlog/src/lib.rs @@ -10,7 +10,7 @@ clippy::clone_on_ref_ptr, clippy::dbg_macro )] -#![allow(clippy::too_many_arguments, clippy::blocks_in_if_conditions)] +#![allow(clippy::too_many_arguments, clippy::blocks_in_conditions)] use std::cmp::Ordering; use std::fmt::Write; diff --git a/git-branchless-submit/src/lib.rs b/git-branchless-submit/src/lib.rs index 6762b3207..5c5a23df8 100644 --- a/git-branchless-submit/src/lib.rs +++ b/git-branchless-submit/src/lib.rs @@ -7,7 +7,7 @@ clippy::clone_on_ref_ptr, clippy::dbg_macro )] -#![allow(clippy::too_many_arguments, clippy::blocks_in_if_conditions)] +#![allow(clippy::too_many_arguments, clippy::blocks_in_conditions)] mod branch_forge; pub mod github; diff --git a/git-branchless-test/src/lib.rs b/git-branchless-test/src/lib.rs index add7cd898..d63088a51 100644 --- a/git-branchless-test/src/lib.rs +++ b/git-branchless-test/src/lib.rs @@ -9,7 +9,7 @@ clippy::clone_on_ref_ptr, clippy::dbg_macro )] -#![allow(clippy::too_many_arguments, clippy::blocks_in_if_conditions)] +#![allow(clippy::too_many_arguments, clippy::blocks_in_conditions)] mod worker; diff --git a/git-branchless-undo/src/lib.rs b/git-branchless-undo/src/lib.rs index 4dc39dcf2..f2090774e 100644 --- a/git-branchless-undo/src/lib.rs +++ b/git-branchless-undo/src/lib.rs @@ -10,7 +10,7 @@ clippy::clone_on_ref_ptr, clippy::dbg_macro )] -#![allow(clippy::too_many_arguments, clippy::blocks_in_if_conditions)] +#![allow(clippy::too_many_arguments, clippy::blocks_in_conditions)] pub mod tui; diff --git a/git-branchless/Cargo.toml b/git-branchless/Cargo.toml index 830f42cf7..298622058 100644 --- a/git-branchless/Cargo.toml +++ b/git-branchless/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT OR Apache-2.0" name = "git-branchless" readme = "../README.md" repository = "https://github.com/arxanas/git-branchless" -rust-version = "1.74" +rust-version = "1.80" version = "0.10.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/git-branchless/src/lib.rs b/git-branchless/src/lib.rs index ae1b74d08..7608cabc1 100644 --- a/git-branchless/src/lib.rs +++ b/git-branchless/src/lib.rs @@ -23,6 +23,6 @@ clippy::clone_on_ref_ptr, clippy::dbg_macro )] -#![allow(clippy::too_many_arguments, clippy::blocks_in_if_conditions)] +#![allow(clippy::too_many_arguments, clippy::blocks_in_conditions)] pub mod commands; diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 94ed87190..583979f5f 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] # Current minimum-supported Rust version -channel = "1.74" +channel = "1.80" profile = "default" diff --git a/scm-bisect/src/lib.rs b/scm-bisect/src/lib.rs index 0eb56aa52..2e7df91cf 100644 --- a/scm-bisect/src/lib.rs +++ b/scm-bisect/src/lib.rs @@ -9,7 +9,7 @@ clippy::clone_on_ref_ptr, clippy::dbg_macro )] -#![allow(clippy::too_many_arguments, clippy::blocks_in_if_conditions)] +#![allow(clippy::too_many_arguments, clippy::blocks_in_conditions)] pub mod basic; pub mod search; From c4d0b0ba5da25fb0391c590db135057e667ae043 Mon Sep 17 00:00:00 2001 From: Matt Hammerly Date: Mon, 21 Apr 2025 10:20:15 -0700 Subject: [PATCH 2/6] feat: support signing commits with gpg, ssh, x509 Originally implemented by Thomas Ip in PR 966. Updated by Matt. Co-authored-by: Thomas Ip --- Cargo.lock | 72 +++++++++++- Cargo.toml | 1 + git-branchless-lib/Cargo.toml | 1 + .../bin/testing/regression_test_record.rs | 2 +- .../src/core/rewrite/execute.rs | 30 ++++- git-branchless-lib/src/git/mod.rs | 2 + git-branchless-lib/src/git/object.rs | 65 ++++++++--- git-branchless-lib/src/git/repo.rs | 29 ++--- git-branchless-lib/src/git/sign.rs | 107 ++++++++++++++++++ git-branchless-lib/src/git/snapshot.rs | 6 +- git-branchless-lib/tests/test_rewrite_plan.rs | 2 + git-branchless-move/src/lib.rs | 2 + git-branchless-opts/src/lib.rs | 52 +++++++++ git-branchless-record/src/lib.rs | 51 ++++++--- git-branchless-reword/src/lib.rs | 6 +- git-branchless-submit/src/phabricator.rs | 6 +- git-branchless-test/src/lib.rs | 10 +- git-branchless/src/commands/amend.rs | 10 +- git-branchless/src/commands/mod.rs | 2 + git-branchless/src/commands/restack.rs | 2 + git-branchless/src/commands/sync.rs | 2 + 21 files changed, 391 insertions(+), 69 deletions(-) create mode 100644 git-branchless-lib/src/git/sign.rs diff --git a/Cargo.lock b/Cargo.lock index 8c78dd0c6..408a51632 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -729,7 +729,7 @@ dependencies = [ "crossterm_winapi", "mio 1.0.3", "parking_lot 0.12.3", - "rustix", + "rustix 0.38.40", "signal-hook", "signal-hook-mio", "winapi", @@ -1059,6 +1059,12 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "env_home" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" + [[package]] name = "env_logger" version = "0.9.3" @@ -1080,12 +1086,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1572,6 +1578,7 @@ dependencies = [ "eyre", "futures", "git2", + "git2-ext", "indicatif", "insta", "itertools 0.14.0", @@ -1833,6 +1840,22 @@ dependencies = [ "url", ] +[[package]] +name = "git2-ext" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4902819113d2dad808b4695e535ea808cdebc152f54bce94ed878ac24a6afd01" +dependencies = [ + "bstr", + "git2", + "itertools 0.14.0", + "log", + "pkg-config", + "shlex", + "tempfile", + "which", +] + [[package]] name = "glob" version = "0.3.2" @@ -2218,6 +2241,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + [[package]] name = "lock_api" version = "0.4.12" @@ -3074,10 +3103,23 @@ dependencies = [ "bitflags 2.6.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.14", "windows-sys 0.52.0", ] +[[package]] +name = "rustix" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.59.0", +] + [[package]] name = "rustversion" version = "1.0.18" @@ -3524,7 +3566,7 @@ dependencies = [ "fastrand", "getrandom 0.3.1", "once_cell", - "rustix", + "rustix 0.38.40", "windows-sys 0.59.0", ] @@ -4065,6 +4107,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "which" +version = "7.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762" +dependencies = [ + "either", + "env_home", + "rustix 1.0.5", + "winsafe", +] + [[package]] name = "winapi" version = "0.3.9" @@ -4387,6 +4441,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "wit-bindgen-rt" version = "0.33.0" diff --git a/Cargo.toml b/Cargo.toml index 2942885c0..128650add 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,6 +70,7 @@ git-branchless-submit = { version = "0.10.0", path = "git-branchless-submit" } git-branchless-test = { version = "0.10.0", path = "git-branchless-test" } git-branchless-undo = { version = "0.10.0", path = "git-branchless-undo" } git2 = { version = "0.20.0", default-features = false } +git2-ext = "0.6.3" glob = "0.3.2" indexmap = "2.7.1" indicatif = { version = "0.17.11", features = ["improved_unicode"] } diff --git a/git-branchless-lib/Cargo.toml b/git-branchless-lib/Cargo.toml index 494a1b72e..594cff3ca 100644 --- a/git-branchless-lib/Cargo.toml +++ b/git-branchless-lib/Cargo.toml @@ -56,6 +56,7 @@ eden_dag = { workspace = true } eyre = { workspace = true } futures = { workspace = true } git2 = { workspace = true } +git2-ext = { workspace = true } indicatif = { workspace = true } itertools = { workspace = true } lazy_static = { workspace = true } diff --git a/git-branchless-lib/bin/testing/regression_test_record.rs b/git-branchless-lib/bin/testing/regression_test_record.rs index edef01eca..f85222833 100644 --- a/git-branchless-lib/bin/testing/regression_test_record.rs +++ b/git-branchless-lib/bin/testing/regression_test_record.rs @@ -76,12 +76,12 @@ fn assert_trees_equal( let message = message.to_str_lossy(); let parents = current_commit.get_parents(); let actual_oid = repo.create_commit( - None, &author, &committer, &message, &actual_tree, parents.iter().collect(), + None, )?; repo.find_commit_or_fail(actual_oid)? }; diff --git a/git-branchless-lib/src/core/rewrite/execute.rs b/git-branchless-lib/src/core/rewrite/execute.rs index 876a6dd1f..ba92eb22e 100644 --- a/git-branchless-lib/src/core/rewrite/execute.rs +++ b/git-branchless-lib/src/core/rewrite/execute.rs @@ -15,7 +15,7 @@ use crate::core::formatting::Pluralize; use crate::core::repo_ext::RepoExt; use crate::git::{ BranchType, CategorizedReferenceName, GitRunInfo, MaybeZeroOid, NonZeroOid, ReferenceName, - Repo, ResolvedReferenceInfo, + Repo, ResolvedReferenceInfo, SignOption, }; use crate::util::{ExitCode, EyreExitOr}; @@ -436,8 +436,8 @@ mod in_memory { use crate::core::rewrite::move_branches; use crate::core::rewrite::plan::{OidOrLabel, RebaseCommand, RebasePlan}; use crate::git::{ - AmendFastOptions, CherryPickFastOptions, CreateCommitFastError, GitRunInfo, MaybeZeroOid, - NonZeroOid, Repo, + self, AmendFastOptions, CherryPickFastOptions, CreateCommitFastError, GitRunInfo, + MaybeZeroOid, NonZeroOid, Repo, }; use crate::util::EyreExitOr; @@ -500,6 +500,7 @@ mod in_memory { force_on_disk: _, resolve_merge_conflicts: _, // May be needed once we can resolve merge conflicts in memory. check_out_commit_options: _, // Caller is responsible for checking out to new HEAD. + sign_option, } = options; let mut current_oid = rebase_plan.first_dest_oid; @@ -537,6 +538,8 @@ mod in_memory { .count(); let (effects, progress) = effects.start_operation(OperationType::RebaseCommits); + let signer = git::get_signer(repo, sign_option)?; + for command in rebase_plan.commands.iter() { match command { RebaseCommand::CreateLabel { label_name } => { @@ -670,12 +673,12 @@ mod in_memory { ); rebased_commit_oid = Some( repo.create_commit( - None, &commit_author, &committer_signature, commit_message, &commit_tree, vec![¤t_commit], + signer.as_deref(), ) .wrap_err("Applying rebased commit")?, ); @@ -802,12 +805,12 @@ mod in_memory { }; let rebased_commit_oid = repo .create_commit( - None, &replacement_commit.get_author(), &committer_signature, replacement_commit_message, &replacement_tree, parents.iter().collect(), + signer.as_deref(), ) .wrap_err("Applying rebased commit")?; @@ -911,6 +914,7 @@ mod in_memory { force_on_disk: _, resolve_merge_conflicts: _, check_out_commit_options, + sign_option: _, } = options; for new_oid in rewritten_oids.values() { @@ -996,6 +1000,7 @@ mod on_disk { force_on_disk: _, resolve_merge_conflicts: _, check_out_commit_options: _, // Checkout happens after rebase has concluded. + sign_option, } = options; let (effects, _progress) = effects.start_operation(OperationType::InitializeRebase); @@ -1113,6 +1118,16 @@ mod on_disk { ) })?; + let gpg_sign_opt_path = rebase_state_dir.join("gpg_sign_opt"); + if let Some(sign_flag) = sign_option.as_rebase_flag(repo)? { + std::fs::write(&gpg_sign_opt_path, sign_flag).wrap_err_with(|| { + format!( + "Writing `gpg_sign_opt` to: {:?}", + gpg_sign_opt_path.as_path() + ) + })?; + } + let end_file_path = rebase_state_dir.join("end"); std::fs::write( end_file_path.as_path(), @@ -1172,6 +1187,7 @@ mod on_disk { force_on_disk: _, resolve_merge_conflicts: _, check_out_commit_options: _, // Checkout happens after rebase has concluded. + sign_option: _, } = options; match write_rebase_state_to_disk(effects, git_run_info, repo, rebase_plan, options)? { @@ -1216,6 +1232,9 @@ pub struct ExecuteRebasePlanOptions { /// If `HEAD` was moved, the options for checking out the new `HEAD` commit. pub check_out_commit_options: CheckOutCommitOptions, + + /// GPG-sign commits. + pub sign_option: SignOption, } /// The result of executing a rebase plan. @@ -1261,6 +1280,7 @@ pub fn execute_rebase_plan( force_on_disk, resolve_merge_conflicts, check_out_commit_options: _, + sign_option: _, } = options; if !force_on_disk { diff --git a/git-branchless-lib/src/git/mod.rs b/git-branchless-lib/src/git/mod.rs index 8b6a09754..8e0dcd02d 100644 --- a/git-branchless-lib/src/git/mod.rs +++ b/git-branchless-lib/src/git/mod.rs @@ -8,6 +8,7 @@ mod oid; mod reference; mod repo; mod run; +mod sign; mod snapshot; mod status; mod test; @@ -27,6 +28,7 @@ pub use repo::{ Result as RepoResult, Time, }; pub use run::{GitRunInfo, GitRunOpts, GitRunResult}; +pub use sign::{get_signer, SignOption}; pub use snapshot::{WorkingCopyChangesType, WorkingCopySnapshot}; pub use status::{FileMode, FileStatus, StatusEntry}; pub use test::{ diff --git a/git-branchless-lib/src/git/object.rs b/git-branchless-lib/src/git/object.rs index adc0c8092..0a455a658 100644 --- a/git-branchless-lib/src/git/object.rs +++ b/git-branchless-lib/src/git/object.rs @@ -4,14 +4,16 @@ use bstr::{BString, ByteSlice}; use cursive::theme::BaseColor; use cursive::utils::markup::StyledString; use git2::message_trailers_bytes; -use tracing::instrument; +use git2_ext::ops::Sign; +use itertools::Itertools; +use tracing::{error, instrument}; use crate::core::formatting::{Glyphs, StyledStringBuilder}; use crate::core::node_descriptors::{ render_node_descriptors, CommitMessageDescriptor, CommitOidDescriptor, NodeObject, Redactor, }; use crate::git::oid::make_non_zero_oid; -use crate::git::repo::{Error, Result, Signature}; +use crate::git::repo::{Error, Repo, Result, Signature}; use crate::git::{NonZeroOid, Time, Tree}; use super::MaybeZeroOid; @@ -291,26 +293,59 @@ impl<'repo> Commit<'repo> { /// Amend this existing commit. /// Returns the OID of the resulting new commit. - #[instrument] + #[instrument(skip(signer))] pub fn amend_commit( &self, - update_ref: Option<&str>, + repo: &'repo Repo, author: Option<&Signature>, committer: Option<&Signature>, message: Option<&str>, tree: Option<&Tree>, + signer: Option<&dyn Sign>, ) -> Result { - let oid = self - .inner - .amend( - update_ref, - author.map(|author| &author.inner), - committer.map(|committer| &committer.inner), - None, - message, - tree.map(|tree| &tree.inner), - ) - .map_err(Error::Amend)?; + let parents = self.get_parents(); + let parents = parents.iter().map(|parent| &parent.inner).collect_vec(); + + let new_author = match author { + Some(author) => &author.inner, + None => &self.inner.author(), + }; + let new_committer = match committer { + Some(committer) => &committer.inner, + None => &self.inner.committer(), + }; + let new_message = match message { + Some(message) => message, + None => match std::str::from_utf8(self.inner.message_bytes()) { + Ok(message) => message, + Err(e) => { + error!( + ?message, + ?e, + "failed to decode git commit message, not valid UTF-8" + ); + return Err(Error::DecodeUtf8 { item: "message" }); + } + }, + }; + let new_tree = match tree { + Some(tree) => &tree.inner, + None => &self.inner.tree().map_err(|err| Error::FindTree { + source: err, + oid: self.inner.tree_id().into(), + })?, + }; + + let oid = git2_ext::ops::commit( + &repo.inner, + new_author, + new_committer, + new_message, + new_tree, + parents.as_slice(), + signer, + ) + .map_err(Error::Amend)?; Ok(make_non_zero_oid(oid)) } } diff --git a/git-branchless-lib/src/git/repo.rs b/git-branchless-lib/src/git/repo.rs index 387f5a2c1..25266775c 100644 --- a/git-branchless-lib/src/git/repo.rs +++ b/git-branchless-lib/src/git/repo.rs @@ -23,6 +23,7 @@ use chrono::{DateTime, Utc}; use cursive::theme::BaseColor; use cursive::utils::markup::StyledString; use git2::DiffOptions; +use git2_ext::ops::Sign; use itertools::Itertools; use thiserror::Error; use tracing::{instrument, warn}; @@ -1254,31 +1255,30 @@ impl Repo { } /// Create a new commit. - #[instrument] + #[instrument(skip(signer))] pub fn create_commit( &self, - update_ref: Option<&str>, author: &Signature, committer: &Signature, message: &str, tree: &Tree, parents: Vec<&Commit>, + signer: Option<&dyn Sign>, ) -> Result { let parents = parents .iter() .map(|commit| &commit.inner) .collect::>(); - let oid = self - .inner - .commit( - update_ref, - &author.inner, - &committer.inner, - message, - &tree.inner, - parents.as_slice(), - ) - .map_err(Error::CreateCommit)?; + let oid = git2_ext::ops::commit( + &self.inner, + &author.inner, + &committer.inner, + message, + &tree.inner, + parents.as_slice(), + signer, + ) + .map_err(Error::CreateCommit)?; Ok(make_non_zero_oid(oid)) } @@ -1463,13 +1463,14 @@ impl Repo { } else { vec![] }; + // TODO: See whether this needs signature support let dehydrated_commit_oid = self.create_commit( - None, &signature, &signature, &message, &dehydrated_tree, parents.iter().collect_vec(), + None, )?; let dehydrated_commit = self.find_commit_or_fail(dehydrated_commit_oid)?; Ok(dehydrated_commit) diff --git a/git-branchless-lib/src/git/sign.rs b/git-branchless-lib/src/git/sign.rs new file mode 100644 index 000000000..0046f34b2 --- /dev/null +++ b/git-branchless-lib/src/git/sign.rs @@ -0,0 +1,107 @@ +use tracing::instrument; + +use super::{Repo, RepoError}; +use crate::git::config::ConfigRead; + +/// GPG-signing option. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SignOption { + /// Sign commits conditionally based on the `commit.gpgsign` configuration and + /// and the key `user.signingkey`. + UseConfig, + /// Sign commits using the key from `user.signingkey` configuration. + UseConfigKey, + /// Sign commits using the provided signing key. + KeyOverride(String), + /// Do not sign commits. + Disable, +} + +impl SignOption { + /// GPG-signing flag to pass to Git. + pub fn as_git_flag(&self) -> Option { + match self { + Self::UseConfig => None, + Self::UseConfigKey => Some("--gpg-sign".to_string()), + Self::KeyOverride(keyid) => Some(format!("--gpg-sign={}", keyid)), + Self::Disable => Some("--no-gpg-sign".to_string()), + } + } + + /// GPG-signing flag to use for interactive rebase + pub fn as_rebase_flag(&self, repo: &Repo) -> eyre::Result> { + Ok(match self { + Self::UseConfig => match repo.get_readonly_config()?.get("commit.gpgsign")? { + Some(true) => Some("-S".to_string()), + Some(false) | None => None, + }, + Self::UseConfigKey => Some("-S".to_string()), + Self::KeyOverride(keyid) => Some(format!("-S{}", keyid)), + Self::Disable => None, + }) + } +} + +/// Get commit signer configured from CLI arguments and repository configurations. +#[allow(clippy::as_conversions)] +#[instrument] +pub fn get_signer( + repo: &Repo, + option: &SignOption, +) -> eyre::Result>> { + match option { + SignOption::UseConfig | SignOption::UseConfigKey => { + if *option == SignOption::UseConfig + && !repo + .get_readonly_config()? + .get_or("commit.gpgsign", false)? + { + return Ok(None); + } + + let config = repo.inner.config().map_err(RepoError::ReadConfig)?; + let signer = git2_ext::ops::UserSign::from_config(&repo.inner, &config) + .map_err(RepoError::ReadConfig)?; + Ok(Some(Box::new(signer) as Box)) + } + SignOption::KeyOverride(keyid) => { + let config = repo.get_readonly_config()?; + let format = config.get_or_else("gpg.format", || "openpgp".to_owned())?; + + let signer = match format.as_str() { + "openpgp" => { + let program = match config.get("gpg.openpgp.program")? { + Some(program) => program, + None => config.get_or_else("gpg.program", || "gpg".to_owned())?, + }; + + Box::new(git2_ext::ops::GpgSign::new(program, keyid.to_string())) + as Box + } + "x509" => { + let program = config.get_or_else("gpg.x509.program", || "gpgsm".to_owned())?; + + Box::new(git2_ext::ops::GpgSign::new(program, keyid.to_string())) + as Box + } + "ssh" => { + let program = + config.get_or_else("gpg.ssh.program", || "ssh-keygen".to_owned())?; + + Box::new(git2_ext::ops::SshSign::new(program, keyid.to_string())) + as Box + } + format => { + return Err(RepoError::ReadConfig(git2::Error::new( + git2::ErrorCode::Invalid, + git2::ErrorClass::Config, + format!("invalid value for gpg.format: {}", format), + )) + .into()) + } + }; + Ok(Some(signer)) + } + SignOption::Disable => Ok(None), + } +} diff --git a/git-branchless-lib/src/git/snapshot.rs b/git-branchless-lib/src/git/snapshot.rs index ca958e5d2..80760bbec 100644 --- a/git-branchless-lib/src/git/snapshot.rs +++ b/git-branchless-lib/src/git/snapshot.rs @@ -213,7 +213,7 @@ branchless: automated working copy snapshot parents }; let commit_oid = - repo.create_commit(None, &signature, &signature, &message, &tree, parents)?; + repo.create_commit(&signature, &signature, &message, &tree, parents, None)?; Ok(WorkingCopySnapshot { base_commit: repo.find_commit_or_fail(commit_oid)?, @@ -364,12 +364,12 @@ branchless: automated working copy snapshot } ); let commit = repo.create_commit( - None, &signature, &signature, &message, &tree_unstaged, Vec::from_iter(head_commit), + None, )?; Ok(commit) } @@ -451,7 +451,6 @@ branchless: automated working copy snapshot } ); let commit_oid = repo.create_commit( - None, &signature, &signature, &message, @@ -460,6 +459,7 @@ branchless: automated working copy snapshot Some(parent_commit) => vec![parent_commit], None => vec![], }, + None, )?; Ok(commit_oid) } diff --git a/git-branchless-lib/tests/test_rewrite_plan.rs b/git-branchless-lib/tests/test_rewrite_plan.rs index 8adc2c335..ce4890977 100644 --- a/git-branchless-lib/tests/test_rewrite_plan.rs +++ b/git-branchless-lib/tests/test_rewrite_plan.rs @@ -15,6 +15,7 @@ use branchless::core::rewrite::{ execute_rebase_plan, BuildRebasePlanOptions, ExecuteRebasePlanOptions, ExecuteRebasePlanResult, RebasePlan, RebasePlanBuilder, RepoResource, }; +use branchless::git::SignOption; use branchless::testing::{make_git, Git}; #[test] @@ -765,6 +766,7 @@ fn create_and_execute_plan( reset: false, render_smartlog: false, }, + sign_option: SignOption::Disable, }; let git_run_info = git.get_git_run_info(); let result = execute_rebase_plan( diff --git a/git-branchless-move/src/lib.rs b/git-branchless-move/src/lib.rs index 78b175b1d..02ed299f1 100644 --- a/git-branchless-move/src/lib.rs +++ b/git-branchless-move/src/lib.rs @@ -261,6 +261,7 @@ pub fn r#move( resolve_merge_conflicts, dump_rebase_constraints, dump_rebase_plan, + ref sign_options, } = *move_options; let now = SystemTime::now(); let event_tx_id = event_log_db.make_transaction_id(now, "move")?; @@ -486,6 +487,7 @@ pub fn r#move( force_on_disk, resolve_merge_conflicts, check_out_commit_options: Default::default(), + sign_option: sign_options.to_owned().into(), }; execute_rebase_plan( effects, diff --git a/git-branchless-opts/src/lib.rs b/git-branchless-opts/src/lib.rs index 37d507dfb..e09c54ea0 100644 --- a/git-branchless-opts/src/lib.rs +++ b/git-branchless-opts/src/lib.rs @@ -94,6 +94,10 @@ pub struct MoveOptions { /// executing it. #[clap(action, long = "debug-dump-rebase-plan")] pub dump_rebase_plan: bool, + + /// GPG-sign commits. + #[clap(flatten)] + pub sign_options: SignOptions, } /// Options for traversing commits. @@ -183,6 +187,46 @@ pub struct SwitchOptions { pub target: Option, } +/// Options for signing commits +#[derive(Args, Debug, Clone)] +pub struct SignOptions { + /// GPG-sign commits. The `keyid` argument is optional and defaults to the committer + /// identity. + #[clap( + short = 'S', + long = "gpg-sign", + value_name = "keyid", + conflicts_with = "no_gpg_sign", + num_args(0..=1), + default_missing_value(""), + )] + pub gpg_sign: Option, + + /// Countermand `commit.gpgSign` configuration variable. + #[clap(long = "no-gpg-sign", conflicts_with = "gpg_sign")] + pub no_gpg_sign: bool, +} + +impl From for lib::git::SignOption { + fn from(value: SignOptions) -> Self { + let SignOptions { + gpg_sign, + no_gpg_sign, + } = value; + if no_gpg_sign { + Self::Disable + } else if let Some(keyid) = gpg_sign { + if keyid.as_str() != "" { + Self::KeyOverride(keyid) + } else { + Self::UseConfigKey + } + } else { + Self::UseConfig + } + } +} + /// Internal use. #[derive(Debug, Parser)] pub enum HookSubcommand { @@ -329,6 +373,10 @@ pub struct RecordArgs { /// After making the new commit, switch back to the previous commit. #[clap(action, short = 's', long = "stash", conflicts_with_all(&["create", "detach"]))] pub stash: bool, + + /// Options for signing commits. + #[clap(flatten)] + pub sign_options: SignOptions, } /// Display a nice graph of the commits you've recently worked on. @@ -639,6 +687,10 @@ pub enum Command { /// use with `git rebase --autosquash`) targeting the supplied commit. #[clap(value_parser, long = "fixup", conflicts_with_all(&["messages", "discard"]))] commit_to_fixup: Option, + + /// GPG-sign commits. + #[clap(flatten)] + sign_options: SignOptions, }, /// `smartlog` command. diff --git a/git-branchless-record/src/lib.rs b/git-branchless-record/src/lib.rs index 2281f6c3c..90f9ce98e 100644 --- a/git-branchless-record/src/lib.rs +++ b/git-branchless-record/src/lib.rs @@ -32,7 +32,7 @@ use lib::core::rewrite::{ }; use lib::git::{ process_diff_for_record, update_index, CategorizedReferenceName, FileMode, GitRunInfo, - MaybeZeroOid, NonZeroOid, Repo, ResolvedReferenceInfo, Stage, UpdateIndexCommand, + MaybeZeroOid, NonZeroOid, Repo, ResolvedReferenceInfo, SignOption, Stage, UpdateIndexCommand, WorkingCopyChangesType, WorkingCopySnapshot, }; use lib::try_exit_code; @@ -58,6 +58,7 @@ pub fn command_main(ctx: CommandContext, args: RecordArgs) -> EyreExitOr<()> { detach, insert, stash, + sign_options, } = args; record( &effects, @@ -68,6 +69,7 @@ pub fn command_main(ctx: CommandContext, args: RecordArgs) -> EyreExitOr<()> { detach, insert, stash, + &sign_options.into(), ) } @@ -81,6 +83,7 @@ fn record( detach: bool, insert: bool, stash: bool, + sign_option: &SignOption, ) -> EyreExitOr<()> { let now = SystemTime::now(); let repo = Repo::from_dir(&git_run_info.working_directory)?; @@ -154,17 +157,23 @@ fn record( &snapshot, event_tx_id, messages, + sign_option, )?); } } else { - let args = { - let mut args = vec!["commit"]; - args.extend(messages.iter().flat_map(|message| ["--message", message])); - if working_copy_changes_type == WorkingCopyChangesType::Unstaged { - args.push("--all"); - } - args - }; + let mut args = vec!["commit"]; + + args.extend(messages.iter().flat_map(|message| ["--message", message])); + + if working_copy_changes_type == WorkingCopyChangesType::Unstaged { + args.push("--all"); + } + + let sign_flag = sign_option.as_git_flag(); + if let Some(flag) = &sign_flag { + args.push(flag); + } + try_exit_code!(git_run_info.run_direct_no_wrapping(Some(event_tx_id), &args)?); } @@ -239,7 +248,8 @@ fn record( effects, git_run_info, now, - event_tx_id + event_tx_id, + sign_option, )?); } @@ -254,6 +264,7 @@ fn record_interactive( snapshot: &WorkingCopySnapshot, event_tx_id: EventTransactionId, messages: Vec, + sign_option: &SignOption, ) -> EyreExitOr<()> { let old_tree = snapshot.commit_stage0.get_tree()?; let new_tree = snapshot.commit_unstaged.get_tree()?; @@ -405,13 +416,17 @@ fn record_interactive( &update_index_script, )?; - let args = { - let mut args = vec!["commit"]; - if !message.is_empty() { - args.extend(["--message", &message]); - } - args - }; + let mut args = vec!["commit"]; + + if !message.is_empty() { + args.extend(["--message", &message]); + } + + let sign_flag = sign_option.as_git_flag(); + if let Some(flag) = &sign_flag { + args.push(flag); + } + git_run_info.run_direct_no_wrapping(Some(event_tx_id), &args) } @@ -421,6 +436,7 @@ fn insert_before_siblings( git_run_info: &GitRunInfo, now: SystemTime, event_tx_id: EventTransactionId, + sign_option: &SignOption, ) -> EyreExitOr<()> { // Reopen the repository since references may have changed. let repo = Repo::from_dir(&git_run_info.working_directory)?; @@ -548,6 +564,7 @@ To proceed anyways, run: git move -f -s 'siblings(.)", force_on_disk: false, resolve_merge_conflicts: false, check_out_commit_options: Default::default(), + sign_option: sign_option.to_owned(), }; let result = execute_rebase_plan( effects, diff --git a/git-branchless-reword/src/lib.rs b/git-branchless-reword/src/lib.rs index 4f003381a..02a1a31a5 100644 --- a/git-branchless-reword/src/lib.rs +++ b/git-branchless-reword/src/lib.rs @@ -42,7 +42,7 @@ use lib::core::rewrite::{ }; use lib::git::{message_prettify, Commit, GitRunInfo, MaybeZeroOid, NonZeroOid, Repo}; -use git_branchless_opts::{ResolveRevsetOptions, Revset}; +use git_branchless_opts::{ResolveRevsetOptions, Revset, SignOptions}; use git_branchless_revset::resolve_commits; /// The commit message(s) provided by the user. @@ -90,6 +90,7 @@ pub fn reword( messages: InitialCommitMessages, git_run_info: &GitRunInfo, force_rewrite_public_commits: bool, + sign_options: SignOptions, ) -> EyreExitOr<()> { let repo = Repo::from_current_dir()?; let references_snapshot = repo.get_references_snapshot()?; @@ -262,7 +263,7 @@ pub fn reword( let message = messages.get(&commit.get_oid()).unwrap(); // This looks funny, but just means "leave everything but the message as is" let replacement_oid = - commit.amend_commit(None, None, None, Some(message.as_str()), None)?; + commit.amend_commit(&repo, None, None, Some(message.as_str()), None, None)?; builder.move_subtree(commit.get_oid(), commit.get_parent_oids())?; builder.replace_commit(commit.get_oid(), replacement_oid)?; } @@ -295,6 +296,7 @@ pub fn reword( reset: false, render_smartlog: false, }, + sign_option: sign_options.into(), }; let result = execute_rebase_plan( effects, diff --git a/git-branchless-submit/src/phabricator.rs b/git-branchless-submit/src/phabricator.rs index b21fb3c89..ede4f56a8 100644 --- a/git-branchless-submit/src/phabricator.rs +++ b/git-branchless-submit/src/phabricator.rs @@ -26,7 +26,9 @@ use lib::core::rewrite::{ execute_rebase_plan, BuildRebasePlanError, BuildRebasePlanOptions, ExecuteRebasePlanOptions, ExecuteRebasePlanResult, RebasePlanBuilder, RebasePlanPermissions, RepoResource, }; -use lib::git::{Commit, GitRunInfo, MaybeZeroOid, NonZeroOid, Repo, RepoError, TestCommand}; +use lib::git::{ + Commit, GitRunInfo, MaybeZeroOid, NonZeroOid, Repo, RepoError, SignOption, TestCommand, +}; use lib::try_exit_code; use lib::util::{ExitCode, EyreExitOr}; use rayon::ThreadPoolBuilder; @@ -359,6 +361,7 @@ impl Forge for PhabricatorForge<'_> { render_smartlog: false, ..Default::default() }, + sign_option: SignOption::Disable, }; let permissions = RebasePlanPermissions::verify_rewrite_set(self.dag, build_options, &commit_set) @@ -607,6 +610,7 @@ Differential Revision: https://phabricator.example.com/D000$(git rev-list --coun render_smartlog: false, ..Default::default() }, + sign_option: SignOption::Disable, }; let permissions = RebasePlanPermissions::verify_rewrite_set(self.dag, build_options, &commit_set) diff --git a/git-branchless-test/src/lib.rs b/git-branchless-test/src/lib.rs index d63088a51..9d1d3a163 100644 --- a/git-branchless-test/src/lib.rs +++ b/git-branchless-test/src/lib.rs @@ -52,8 +52,9 @@ use lib::core::rewrite::{ use lib::git::{ get_latest_test_command_path, get_test_locks_dir, get_test_tree_dir, get_test_worktrees_dir, make_test_command_slug, Commit, ConfigRead, GitRunInfo, GitRunResult, MaybeZeroOid, NonZeroOid, - Repo, SerializedNonZeroOid, SerializedTestResult, TestCommand, WorkingCopyChangesType, - TEST_ABORT_EXIT_CODE, TEST_INDETERMINATE_EXIT_CODE, TEST_SUCCESS_EXIT_CODE, + Repo, SerializedNonZeroOid, SerializedTestResult, SignOption, TestCommand, + WorkingCopyChangesType, TEST_ABORT_EXIT_CODE, TEST_INDETERMINATE_EXIT_CODE, + TEST_SUCCESS_EXIT_CODE, }; use lib::try_exit_code; use lib::util::{get_sh, ExitCode, EyreExitOr}; @@ -388,6 +389,7 @@ BUG: Expected resolved_interactive ({resolved_interactive:?}) to match interacti resolve_merge_conflicts, dump_rebase_constraints, dump_rebase_plan, + sign_options, } = move_options; let force_in_memory = true; @@ -416,6 +418,7 @@ BUG: Expected resolved_interactive ({resolved_interactive:?}) to match interacti render_smartlog: false, ..Default::default() }, + sign_option: sign_options.to_owned().into(), }; let permissions = match RebasePlanPermissions::verify_rewrite_set(dag, build_options, commits)? { @@ -731,6 +734,7 @@ fn set_abort_trap( render_smartlog: false, ..Default::default() }, + sign_option: SignOption::Disable, }, )? { ExecuteRebasePlanResult::Succeeded { rewritten_oids: _ } => { @@ -1983,12 +1987,12 @@ fn apply_fixes( .try_collect()?; let fixed_tree = repo.find_tree_or_fail(fixed_tree_oid)?; let fixed_commit_oid = repo.create_commit( - None, &original_commit.get_author(), &original_commit.get_committer(), commit_message, &fixed_tree, parents.iter().collect(), + None, )?; if original_commit_oid == fixed_commit_oid { continue; diff --git a/git-branchless/src/commands/amend.rs b/git-branchless/src/commands/amend.rs index f20fa1251..04c7f4539 100644 --- a/git-branchless/src/commands/amend.rs +++ b/git-branchless/src/commands/amend.rs @@ -26,6 +26,7 @@ use lib::core::rewrite::{ execute_rebase_plan, move_branches, BuildRebasePlanOptions, ExecuteRebasePlanOptions, ExecuteRebasePlanResult, RebasePlanBuilder, RebasePlanPermissions, RepoResource, }; +use lib::git::get_signer; use lib::git::{AmendFastOptions, GitRunInfo, MaybeZeroOid, Repo, ResolvedReferenceInfo}; use lib::try_exit_code; use lib::util::{ExitCode, EyreExitOr}; @@ -155,12 +156,16 @@ pub fn amend( ) }; + let sign_option = move_options.sign_options.to_owned().into(); + let signer = get_signer(&repo, &sign_option)?; + let amended_commit_oid = head_commit.amend_commit( - None, + &repo, Some(&author), Some(&committer), None, Some(&amended_tree), + signer.as_deref(), )?; // Switch to the new commit and move any branches. This is kind of a hack: @@ -265,12 +270,12 @@ pub fn amend( ) })?; let reparented_descendant_oid = repo.create_commit( - None, &descendant_commit.get_author(), &descendant_commit.get_committer(), descendant_message, &descendant_commit.get_tree()?, parents.iter().collect(), + signer.as_deref(), )?; builder.replace_commit(descendant_oid, reparented_descendant_oid)?; } @@ -300,6 +305,7 @@ pub fn amend( reset: false, render_smartlog: false, }, + sign_option, }; match execute_rebase_plan( effects, diff --git a/git-branchless/src/commands/mod.rs b/git-branchless/src/commands/mod.rs index d0ff79eaa..ee7d8c1d2 100644 --- a/git-branchless/src/commands/mod.rs +++ b/git-branchless/src/commands/mod.rs @@ -152,6 +152,7 @@ fn command_main(ctx: CommandContext, opts: Opts) -> EyreExitOr<()> { force_rewrite_public_commits, discard, commit_to_fixup, + sign_options, } => { let messages = if discard { git_branchless_reword::InitialCommitMessages::Discard @@ -167,6 +168,7 @@ fn command_main(ctx: CommandContext, opts: Opts) -> EyreExitOr<()> { messages, &git_run_info, force_rewrite_public_commits, + sign_options, )? } diff --git a/git-branchless/src/commands/restack.rs b/git-branchless/src/commands/restack.rs index 0b6c72839..802564b1c 100644 --- a/git-branchless/src/commands/restack.rs +++ b/git-branchless/src/commands/restack.rs @@ -312,6 +312,7 @@ pub fn restack( resolve_merge_conflicts, dump_rebase_constraints, dump_rebase_plan, + ref sign_options, } = *move_options; let build_options = BuildRebasePlanOptions { force_rewrite_public_commits, @@ -331,6 +332,7 @@ pub fn restack( reset: false, render_smartlog: false, }, + sign_option: sign_options.to_owned().into(), }; let pool = ThreadPoolBuilder::new().build()?; let repo_pool = RepoResource::new_pool(&repo)?; diff --git a/git-branchless/src/commands/sync.rs b/git-branchless/src/commands/sync.rs index 5106c1841..756d019e1 100644 --- a/git-branchless/src/commands/sync.rs +++ b/git-branchless/src/commands/sync.rs @@ -76,6 +76,7 @@ pub fn sync( resolve_merge_conflicts, dump_rebase_constraints, dump_rebase_plan, + ref sign_options, } = *move_options; let build_options = BuildRebasePlanOptions { force_rewrite_public_commits, @@ -97,6 +98,7 @@ pub fn sync( reset: false, render_smartlog: false, }, + sign_option: sign_options.to_owned().into(), }; let thread_pool = ThreadPoolBuilder::new().build()?; let repo_pool = RepoResource::new_pool(&repo)?; From 1a85de75fe0c8377b4c31b17cf8d118973e72d1c Mon Sep 17 00:00:00 2001 From: Abhijay Saran Date: Sat, 12 Apr 2025 15:40:49 -0700 Subject: [PATCH 3/6] feat: add support for git branchless --show-signature --- .../src/core/node_descriptors.rs | 73 +++++++++++++++++++ git-branchless-opts/src/lib.rs | 4 + git-branchless-smartlog/src/lib.rs | 9 ++- 3 files changed, 85 insertions(+), 1 deletion(-) diff --git a/git-branchless-lib/src/core/node_descriptors.rs b/git-branchless-lib/src/core/node_descriptors.rs index bc3b083ff..2a7c7a188 100644 --- a/git-branchless-lib/src/core/node_descriptors.rs +++ b/git-branchless-lib/src/core/node_descriptors.rs @@ -516,6 +516,79 @@ impl NodeDescriptor for RelativeTimeDescriptor { } } +/// Display the GPG signature status for a commit. +#[derive(Debug)] +pub struct SignatureStatusDescriptor { + is_enabled: bool, + repo_path: String, +} + +impl SignatureStatusDescriptor { + /// Constructor. + pub fn new(repo: &Repo, is_enabled: bool) -> eyre::Result { + let repo_path = repo.get_path().to_string_lossy().to_string(); + Ok(Self { + is_enabled, + repo_path, + }) + } +} + +impl NodeDescriptor for SignatureStatusDescriptor { + #[instrument] + fn describe_node( + &mut self, + _glyphs: &Glyphs, + object: &NodeObject, + ) -> eyre::Result> { + if !self.is_enabled { + return Ok(None); + } + + let oid = object.get_oid().to_string(); + + // Use git command-line to get signature information as git2 doesn't + // expose GPG signature verification directly + let output = std::process::Command::new("git") + .args(["-C", &self.repo_path, "verify-commit", &oid, "--raw"]) + .output(); + + match output { + Ok(output) => { + if output.status.success() { + // Good signature + let mut result = StyledString::new(); + result.append_styled("[G]", BaseColor::Green.light()); + Ok(Some(result)) + } else { + // Bad signature + let stderr = String::from_utf8_lossy(&output.stderr); + if stderr.contains("BAD signature") { + let mut result = StyledString::new(); + result.append_styled("[B]", BaseColor::Red.light()); + Ok(Some(result)) + } else if stderr.contains("No signature") { + let mut result = StyledString::new(); + result.append_styled("[N]", BaseColor::Yellow.light()); + Ok(Some(result)) + } else { + // Unknown error + let mut result = StyledString::new(); + result.append_styled("[?]", BaseColor::Magenta.light()); + Ok(Some(result)) + } + } + } + Err(_) => { + // Error running command + let mut result = StyledString::new(); + result.append_styled("[E]", BaseColor::Red.light()); + Ok(Some(result)) + } + } + } +} + #[cfg(test)] mod tests { use std::ops::{Add, Sub}; diff --git a/git-branchless-opts/src/lib.rs b/git-branchless-opts/src/lib.rs index e09c54ea0..69cbf038f 100644 --- a/git-branchless-opts/src/lib.rs +++ b/git-branchless-opts/src/lib.rs @@ -406,6 +406,10 @@ pub struct SmartlogArgs { /// Options for resolving revset expressions. #[clap(flatten)] pub resolve_revset_options: ResolveRevsetOptions, + + /// Show GPG signature status for each commit. + #[clap(action, long = "show-signature")] + pub show_signature: bool, } /// The Git hosting provider to use, called a "forge". diff --git a/git-branchless-smartlog/src/lib.rs b/git-branchless-smartlog/src/lib.rs index 43ccc2bd0..d9bb96901 100644 --- a/git-branchless-smartlog/src/lib.rs +++ b/git-branchless-smartlog/src/lib.rs @@ -34,7 +34,7 @@ use lib::core::formatting::Pluralize; use lib::core::node_descriptors::{ BranchesDescriptor, CommitMessageDescriptor, CommitOidDescriptor, DifferentialRevisionDescriptor, ObsolescenceExplanationDescriptor, Redactor, - RelativeTimeDescriptor, + RelativeTimeDescriptor, SignatureStatusDescriptor, }; use lib::git::{GitRunInfo, Repo}; @@ -753,6 +753,9 @@ mod render { /// Normally HEAD and the main branch are included. Set this to exclude them. pub exact: bool, + + /// Show GPG signature status for each commit. + pub show_signature: bool, } } @@ -769,6 +772,7 @@ pub fn smartlog( resolve_revset_options, reverse, exact, + show_signature, } = options; let repo = Repo::from_dir(&git_run_info.working_directory)?; @@ -845,6 +849,7 @@ pub fn smartlog( &Redactor::Disabled, )?, &mut DifferentialRevisionDescriptor::new(&repo, &Redactor::Disabled)?, + &mut SignatureStatusDescriptor::new(&repo, show_signature)?, &mut CommitMessageDescriptor::new(&Redactor::Disabled)?, ], )? @@ -916,6 +921,7 @@ pub fn command_main(ctx: CommandContext, args: SmartlogArgs) -> EyreExitOr<()> { resolve_revset_options, reverse, exact, + show_signature, } = args; smartlog( @@ -927,6 +933,7 @@ pub fn command_main(ctx: CommandContext, args: SmartlogArgs) -> EyreExitOr<()> { resolve_revset_options, reverse, exact, + show_signature, }, ) } From 1b27a5bb1e7ec38e3f2d183346da419d60224cc2 Mon Sep 17 00:00:00 2001 From: Abhijay Saran Date: Mon, 14 Apr 2025 19:11:52 -0700 Subject: [PATCH 4/6] feat: adds smartlog tests --- .../src/core/node_descriptors.rs | 30 ++++++ .../tests/test_smartlog.rs | 92 +++++++++++++++++++ 2 files changed, 122 insertions(+) diff --git a/git-branchless-lib/src/core/node_descriptors.rs b/git-branchless-lib/src/core/node_descriptors.rs index 2a7c7a188..e9fc1d39a 100644 --- a/git-branchless-lib/src/core/node_descriptors.rs +++ b/git-branchless-lib/src/core/node_descriptors.rs @@ -592,6 +592,7 @@ impl NodeDescriptor for SignatureStatusDescriptor { #[cfg(test)] mod tests { use std::ops::{Add, Sub}; + use std::str::FromStr; use std::time::Duration; use super::*; @@ -650,4 +651,33 @@ Differential Revision: phabricator.com/D123"; Ok(()) } + + #[test] + fn test_signature_status_descriptor() -> eyre::Result<()> { + // Create a mock where is_enabled is false + let mut descriptor = SignatureStatusDescriptor { + is_enabled: false, + repo_path: String::from("/tmp"), + }; + + // Create a mock object - doesn't matter what it contains since is_enabled is false + let object = NodeObject::GarbageCollected { + oid: NonZeroOid::from_str("1234567890123456789012345678901234567890").unwrap(), + }; + + // With is_enabled = false, it should return None + let glyphs = Glyphs::text(); + let result = descriptor.describe_node(&glyphs, &object)?; + assert!(result.is_none()); + + // Now test with is_enabled = true + descriptor.is_enabled = true; + + // We can't easily test the actual git command execution in a unit test, + // but we can verify that the method doesn't panic and returns a result + let result = descriptor.describe_node(&glyphs, &object); + assert!(result.is_ok()); + + Ok(()) + } } diff --git a/git-branchless-smartlog/tests/test_smartlog.rs b/git-branchless-smartlog/tests/test_smartlog.rs index 2b53bd6e3..d82eb30cc 100644 --- a/git-branchless-smartlog/tests/test_smartlog.rs +++ b/git-branchless-smartlog/tests/test_smartlog.rs @@ -812,3 +812,95 @@ fn test_exact() -> eyre::Result<()> { Ok(()) } + +#[test] +fn test_show_signature_flag() -> eyre::Result<()> { + let git = make_git()?; + + git.init_repo()?; + + // Just create a regular commit without trying to sign it + git.commit_file("signed_file", 1)?; + git.commit_file("unsigned_file", 2)?; + + // Run smartlog without --show-signature flag + { + let stdout = git.smartlog()?; + // Without the flag, the signature indicators shouldn't be visible + // However, we can't reliably check for absence of brackets since they might be used elsewhere + // So we'll just ensure the command runs successfully + assert!(!stdout.is_empty(), "Smartlog output should not be empty"); + } + + // Run smartlog with --show-signature flag + { + let (stdout, _) = git.branchless("smartlog", &["--show-signature"])?; + // With the flag, the command should run successfully + assert!(!stdout.is_empty(), "Smartlog output should not be empty"); + // The signature status indicators should be present in some form, + // but we don't validate exactly which status since that depends on the environment + } + + Ok(()) +} + +#[test] +fn test_show_signature_snapshot() -> eyre::Result<()> { + let git = make_git()?; + + git.init_repo()?; + + // Create regular commits without trying to sign them + git.commit_file("signed_file", 1)?; + + // Create a branch and make another commit + git.run(&["checkout", "-b", "branch1", "master"])?; + git.commit_file("another_file", 2)?; + + // Run smartlog with --show-signature flag + let (stdout, _) = git.branchless("smartlog", &["--show-signature"])?; + + // Simply verify the command runs without error + assert!(!stdout.is_empty(), "Smartlog output should not be empty"); + + Ok(()) +} + +#[test] +fn test_show_signature_argument_parsing() -> eyre::Result<()> { + let git = make_git()?; + git.init_repo()?; + + // Test that invalid flag combination reports error + // For example, combine with a flag that doesn't make sense (if any) + // If there are no incompatible flags, we can at least test that the flag is recognized + + // Test help output includes the flag + let (help_stdout, _) = git.branchless("smartlog", &["--help"])?; + assert!( + help_stdout.contains("--show-signature"), + "Help output should include --show-signature option" + ); + + // Test that the flag is recognized (should execute without error) + let result = git.branchless("smartlog", &["--show-signature"]); + assert!( + result.is_ok(), + "The --show-signature flag should be recognized" + ); + + // Test that the flag works when combined with other flags + let result = git.branchless("smartlog", &["--show-signature", "--exact"]); + assert!( + result.is_ok(), + "The --show-signature flag should work with --exact" + ); + + let result = git.branchless("smartlog", &["--show-signature", "--reverse"]); + assert!( + result.is_ok(), + "The --show-signature flag should work with --reverse" + ); + + Ok(()) +} From ceb95f24b89ab1572dffb5456ee5a1fb3970d751 Mon Sep 17 00:00:00 2001 From: Matt Hammerly Date: Sat, 12 Apr 2025 15:50:42 -0700 Subject: [PATCH 5/6] fix: update test snapshots to include show_signature argument --- git-branchless/tests/test_init.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/git-branchless/tests/test_init.rs b/git-branchless/tests/test_init.rs index 82be1b2d1..17c93d21f 100644 --- a/git-branchless/tests/test_init.rs +++ b/git-branchless/tests/test_init.rs @@ -305,7 +305,7 @@ fn test_main_branch_not_found_error_message() -> eyre::Result<()> { let stderr = trim_lines(stderr); let stderr = console::strip_ansi_codes(&stderr); let stderr = location_trace_re.replace_all(&stderr, "some/file/path.rs:123"); - insta::assert_snapshot!(stderr, @r###" + insta::assert_snapshot!(stderr, @r#" The application panicked (crashed). Message: A fatal error occurred: 0: Could not find repository main branch @@ -317,9 +317,9 @@ fn test_main_branch_not_found_error_message() -> eyre::Result<()> { 0: branchless::core::eventlog::from_event_log_db with effects= repo=/.git/"> event_log_db=/.git/branchless/db.sqlite3")> at some/file/path.rs:123 - 1: git_branchless_smartlog::smartlog with effects= git_run_info= options=SmartlogOptions { event_id: None, revset: None, resolve_revset_options: ResolveRevsetOptions { show_hidden_commits: false }, reverse: false, exact: false } + 1: git_branchless_smartlog::smartlog with effects= git_run_info= options=SmartlogOptions { event_id: None, revset: None, resolve_revset_options: ResolveRevsetOptions { show_hidden_commits: false }, reverse: false, exact: false, show_signature: false } at some/file/path.rs:123 - 2: git_branchless_smartlog::command_main with ctx=CommandContext { effects: , git_run_info: } args=SmartlogArgs { event_id: None, revset: None, reverse: false, exact: false, resolve_revset_options: ResolveRevsetOptions { show_hidden_commits: false } } + 2: git_branchless_smartlog::command_main with ctx=CommandContext { effects: , git_run_info: } args=SmartlogArgs { event_id: None, revset: None, reverse: false, exact: false, resolve_revset_options: ResolveRevsetOptions { show_hidden_commits: false }, show_signature: false } at some/file/path.rs:123 Suggestion: @@ -339,7 +339,7 @@ fn test_main_branch_not_found_error_message() -> eyre::Result<()> { Backtrace omitted. Run with RUST_BACKTRACE=1 environment variable to display it. Run with RUST_BACKTRACE=full to include source snippets. - "###); + "#); insta::assert_snapshot!(stdout, @""); Ok(()) From 7d36b9573602757c9a834b60c47c7d94f4281578 Mon Sep 17 00:00:00 2001 From: Matt Hammerly Date: Mon, 21 Apr 2025 11:08:53 -0700 Subject: [PATCH 6/6] create tests for commit signatures --- .github/workflows/linux-git-devel.yml | 3 + .github/workflows/linux.yml | 3 + git-branchless-lib/Cargo.toml | 3 + git-branchless-lib/src/core/config.rs | 57 ++++++ ...BD7243618F2F5FB77B071DB3B9DB339CA11313.rev | 35 ++++ ...FB31B395BD400708E4F73B5A0DB2B9B5C97CA5.key | 42 +++++ ...4F2BB69A7933D919F95FEFE7C785EF3B2D3624.key | 42 +++++ .../tests/test_data/gpg/pubring.kbx | Bin 0 -> 1980 bytes .../tests/test_data/gpg/pubring.kbx~ | Bin 0 -> 32 bytes .../tests/test_data/gpg/trustdb.gpg | Bin 0 -> 1280 bytes .../tests/test_data/ssh/id_ed25519 | 7 + .../tests/test_data/ssh/id_ed25519.pub | 1 + .../tests/test_data/ssh/ssh-allowed-signers | 1 + .../tests/test_data/x509/ca.crt | 30 +++ .../tests/test_data/x509/ca.key | 51 ++++++ .../tests/test_data/x509/ca.p12 | Bin 0 -> 4104 bytes .../tests/test_data/x509/generate.sh | 48 +++++ .../tests/test_data/x509/git.crt | 32 ++++ .../tests/test_data/x509/git.csr | 26 +++ .../tests/test_data/x509/git.key | 51 ++++++ .../tests/test_data/x509/git.p12 | Bin 0 -> 4198 bytes .../tests/test_data/x509/gpgsm.conf | 7 + ...CE1B8A4A58B3CA21F7E57C764B0E3CF0A0C62C.key | 54 ++++++ ...8D249FE838FA4E490BA732F0ED514E8DD61EB6.key | 54 ++++++ .../tests/test_data/x509/pubring.kbx | Bin 0 -> 3114 bytes .../tests/test_data/x509/pubring.kbx~ | Bin 0 -> 1505 bytes .../tests/test_data/x509/trustlist.txt | 2 + git-branchless-lib/tests/test_sign.rs | 173 ++++++++++++++++++ 28 files changed, 722 insertions(+) create mode 100644 git-branchless-lib/tests/test_data/gpg/openpgp-revocs.d/F0BD7243618F2F5FB77B071DB3B9DB339CA11313.rev create mode 100644 git-branchless-lib/tests/test_data/gpg/private-keys-v1.d/06FB31B395BD400708E4F73B5A0DB2B9B5C97CA5.key create mode 100644 git-branchless-lib/tests/test_data/gpg/private-keys-v1.d/E54F2BB69A7933D919F95FEFE7C785EF3B2D3624.key create mode 100644 git-branchless-lib/tests/test_data/gpg/pubring.kbx create mode 100644 git-branchless-lib/tests/test_data/gpg/pubring.kbx~ create mode 100644 git-branchless-lib/tests/test_data/gpg/trustdb.gpg create mode 100755 git-branchless-lib/tests/test_data/ssh/id_ed25519 create mode 100644 git-branchless-lib/tests/test_data/ssh/id_ed25519.pub create mode 100644 git-branchless-lib/tests/test_data/ssh/ssh-allowed-signers create mode 100644 git-branchless-lib/tests/test_data/x509/ca.crt create mode 100644 git-branchless-lib/tests/test_data/x509/ca.key create mode 100644 git-branchless-lib/tests/test_data/x509/ca.p12 create mode 100755 git-branchless-lib/tests/test_data/x509/generate.sh create mode 100644 git-branchless-lib/tests/test_data/x509/git.crt create mode 100644 git-branchless-lib/tests/test_data/x509/git.csr create mode 100644 git-branchless-lib/tests/test_data/x509/git.key create mode 100644 git-branchless-lib/tests/test_data/x509/git.p12 create mode 100644 git-branchless-lib/tests/test_data/x509/gpgsm.conf create mode 100644 git-branchless-lib/tests/test_data/x509/private-keys-v1.d/04CE1B8A4A58B3CA21F7E57C764B0E3CF0A0C62C.key create mode 100644 git-branchless-lib/tests/test_data/x509/private-keys-v1.d/888D249FE838FA4E490BA732F0ED514E8DD61EB6.key create mode 100644 git-branchless-lib/tests/test_data/x509/pubring.kbx create mode 100644 git-branchless-lib/tests/test_data/x509/pubring.kbx~ create mode 100644 git-branchless-lib/tests/test_data/x509/trustlist.txt create mode 100644 git-branchless-lib/tests/test_sign.rs diff --git a/.github/workflows/linux-git-devel.yml b/.github/workflows/linux-git-devel.yml index 1a27ac3e7..828e3a56f 100644 --- a/.github/workflows/linux-git-devel.yml +++ b/.github/workflows/linux-git-devel.yml @@ -66,6 +66,9 @@ jobs: run: | export TEST_GIT="$PWD"/git-master/git export TEST_GIT_EXEC_PATH=$(dirname "$TEST_GIT") + export TEST_GPG=$(which gpg) + export TEST_GPGSM=$(which gpgsm) + export TEST_SSH_KEYGEN=$(which ssh-keygen) (cd git-branchless && cargo test --all-features --examples --tests --workspace --no-fail-fast) - name: Run Rust tests on Git `next` diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 514226d15..acd8951ef 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -89,6 +89,9 @@ jobs: run: | export TEST_GIT="$PWD"/git export TEST_GIT_EXEC_PATH=$(dirname "$TEST_GIT") + export TEST_GPG=$(which gpg) + export TEST_GPGSM=$(which gpgsm) + export TEST_SSH_KEYGEN=$(which ssh-keygen) cargo test --all-features --examples --tests --workspace --no-fail-fast # Note that `--doc` can't be combined with other tests. diff --git a/git-branchless-lib/Cargo.toml b/git-branchless-lib/Cargo.toml index 594cff3ca..3b7087a07 100644 --- a/git-branchless-lib/Cargo.toml +++ b/git-branchless-lib/Cargo.toml @@ -17,6 +17,9 @@ name = "branchless" [features] default = [] integration-test-bin = [] +test_gpg = [] +test_ssh = [] +test_x509 = [] [[bench]] harness = false diff --git a/git-branchless-lib/src/core/config.rs b/git-branchless-lib/src/core/config.rs index 248525a36..e03d28c1d 100644 --- a/git-branchless-lib/src/core/config.rs +++ b/git-branchless-lib/src/core/config.rs @@ -318,6 +318,16 @@ pub mod env_vars { /// See . pub const TEST_GIT_EXEC_PATH: &str = "TEST_GIT_EXEC_PATH"; + /// Path to the `ssh-keygen` executable to use for signing functionality in + /// tests. + pub const TEST_SSH_KEYGEN: &str = "TEST_SSH_KEYGEN"; + + /// Path to the `gpg` executable to use for signing functionality in tests. + pub const TEST_GPG: &str = "TEST_GPG"; + + /// Path to the `gpgsm` executable to use for signing functionality in tests. + pub const TEST_GPGSM: &str = "TEST_GPGSM"; + /// Specifies `git-branchless` subcommands to invoke directly. /// /// For example, `TEST_SEPARATE_COMMAND_BINARIES=init test`, this function @@ -381,6 +391,53 @@ or set `env.{0}` in your `config.toml` \ Ok(git_exec_path) } + /// Get the path to the `ssh-keygen` executable for testing. + #[instrument] + pub fn get_path_to_ssh_keygen() -> eyre::Result { + let path_to_ssh_keygen = std::env::var_os(TEST_SSH_KEYGEN).ok_or_else(|| { + eyre::eyre!( + "No path to `ssh-keygen` executable was set. \ +Try running as: `{0}=$(which ssh-keygen) cargo test ...` \ +or set `env.{0}` in your `config.toml` \ +(see https://doc.rust-lang.org/cargo/reference/config.html)", + TEST_SSH_KEYGEN, + ) + })?; + let path_to_ssh_keygen = PathBuf::from(&path_to_ssh_keygen); + Ok(path_to_ssh_keygen) + } + + /// Get the path to the `gpg` executable for testing. + #[instrument] + pub fn get_path_to_gpg() -> eyre::Result { + let path_to_gpg = std::env::var_os(TEST_GPG).ok_or_else(|| { + eyre::eyre!( + "No path to `gpg` executable was set. \ +Try running as: `{0}=$(which gpg) cargo test ...` \ +or set `env.{0}` in your `config.toml` \ +(see https://doc.rust-lang.org/cargo/reference/config.html)", + TEST_GPG, + ) + })?; + let path_to_gpg = PathBuf::from(&path_to_gpg); + Ok(path_to_gpg) + } + /// Get the path to the `gpgsm` executable for testing. + #[instrument] + pub fn get_path_to_gpgsm() -> eyre::Result { + let path_to_gpgsm = std::env::var_os(TEST_GPGSM).ok_or_else(|| { + eyre::eyre!( + "No path to `gpgsm` executable was set. \ +Try running as: `{0}=$(which gpgsm) cargo test ...` \ +or set `env.{0}` in your `config.toml` \ +(see https://doc.rust-lang.org/cargo/reference/config.html)", + TEST_GPGSM, + ) + })?; + let path_to_gpgsm = PathBuf::from(&path_to_gpgsm); + Ok(path_to_gpgsm) + } + /// Determine whether the specified binary should be run separately. See /// [`TEST_SEPARATE_COMMAND_BINARIES`] for more details. #[instrument] diff --git a/git-branchless-lib/tests/test_data/gpg/openpgp-revocs.d/F0BD7243618F2F5FB77B071DB3B9DB339CA11313.rev b/git-branchless-lib/tests/test_data/gpg/openpgp-revocs.d/F0BD7243618F2F5FB77B071DB3B9DB339CA11313.rev new file mode 100644 index 000000000..1e2c569a7 --- /dev/null +++ b/git-branchless-lib/tests/test_data/gpg/openpgp-revocs.d/F0BD7243618F2F5FB77B071DB3B9DB339CA11313.rev @@ -0,0 +1,35 @@ +This is a revocation certificate for the OpenPGP key: + +pub rsa3072 2025-04-19 [S] + F0BD7243618F2F5FB77B071DB3B9DB339CA11313 +uid Testy McTestface (Test GPG key) + +A revocation certificate is a kind of "kill switch" to publicly +declare that a key shall not anymore be used. It is not possible +to retract such a revocation certificate once it has been published. + +Use it to revoke this key in case of a compromise or loss of +the secret key. However, if the secret key is still accessible, +it is better to generate a new revocation certificate and give +a reason for the revocation. For details see the description of +of the gpg command "--generate-revocation" in the GnuPG manual. + +To avoid an accidental use of this file, a colon has been inserted +before the 5 dashes below. Remove this colon with a text editor +before importing and publishing this revocation certificate. + +:-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: This is a revocation certificate + +iQG2BCABCgAgFiEE8L1yQ2GPL1+3ewcds7nbM5yhExMFAmgDBygCHQAACgkQs7nb +M5yhExPKhwv/VQE1gperI+R3aXvjaTv9O3LhjldNnOjK3X0V+2tP8MDDvoYA0FXO +Ncin/eP/O0BBfrr5/5mGfdlfOZQ7Lah9oA7RrVo2qoM+ybKaZwDqtipKAOetEAF1 +ldxplrgUwSu6960pUsxP2mCHIXGfYT+xnCuyiEbTUf7RtsfU/EShunzr6EuQ2gsO +cSZnKusnIPUhTU8sYYzspg+bCdlGX1eVwrIz7ebh3g+4Gk15LbRrbJw8gOQZRwlm +mPelxQUAoGD2sVGrwIEivWuJSVveW7vFMuAeAUZcL6tyZTwbnydUEoIZtPbkpc9+ +rC9YpJnfS4vKxGwTNjaLMSLhIPPnK/cP/aMq0TJNehBcjymrd7wc9flJLKj02xoS +MxnuaSmqGxfSV14/Gn447krvt9RIJznYMVCgcMmNqKUqEqpSGlDK9/EuJYnm1AsF +M57sb0I6uMgyfxcfzoO/mz2f5bbEs+BuBr+4x4T0ESKFoOI4OKJaHvbX6iCpvkwd +LyX9m476O1WY +=aqZr +-----END PGP PUBLIC KEY BLOCK----- diff --git a/git-branchless-lib/tests/test_data/gpg/private-keys-v1.d/06FB31B395BD400708E4F73B5A0DB2B9B5C97CA5.key b/git-branchless-lib/tests/test_data/gpg/private-keys-v1.d/06FB31B395BD400708E4F73B5A0DB2B9B5C97CA5.key new file mode 100644 index 000000000..b0935d192 --- /dev/null +++ b/git-branchless-lib/tests/test_data/gpg/private-keys-v1.d/06FB31B395BD400708E4F73B5A0DB2B9B5C97CA5.key @@ -0,0 +1,42 @@ +Created: 20250419T021459 +Key: (private-key (rsa (n #00AAD367F33B2C6D9B01976C70F2A90A5D4ECA9BD06A + A9AA36016A3C050A56CA25BD0D609279A5AA924AB786A651E19C346C4353FD916B80FC + FF29F95DF56A74DC50935F80D1F40D669328E78DE02AD39CAA15421B92E11225191A3A + 4AF47BF2C7A1F478D465B5FBB96EA5D3B0B14B0C99A7B25A5DEDDA802FFB1E227B5E62 + F09FA6750A5774FDCABD2BA48A1C7835386BAA2641F52DDF704A743C6F8419F87004BE + B8F93241976E63C5A042F8DED92D1B68E2AD9D01BA562354119F9F7C36B5DA841B0701 + 7AD6DD09896B452D9BC64760EC0FD6935DA1EFD2DD7AABB11A937B61B8F82B02FB2929 + BB9B3FADE16E12792ADFD3F45E61F10A6E2758D4F6818E8525D6132E2B270AE99FEFAD + E5D8A98579F59FC84951B2ED2748507281480D412CA73BC811D37B82636646775C3E48 + 25E0490EF2AEED1683AC3F32D3BD1422F36AB458D0A21648F8BF92AA2AD8F2E0EB024A + F8EBB84C20157621F0EBCED60332274C19069F4AF8F02694356915E80622C112E75F33 + DEA38290CF684D9B456B5D5D85#)(e #010001#)(d + #5216EC65ACC2715B633CE69E32663C3651A6389143A88BC48F12838C32873976402E + BD013696FF6C749E5404F9E52E80DCA3D02952A7F420DFE7C64F3A1FB1CFABA2DE6A93 + BA317460F46FADB4933004A1913CA6EE82DD0AD9D0CA4179D2547D4D9D2331823D1D1D + E387A9BE47BC966E0271C94E48FE4ECA7FE4DFD2242DEC8EF3D95BA2627491682DA050 + C45499D2A69E69EB194CB58A8B24E763F979D772D13CEBBBC8C44F667B689D5681ACE9 + 756D2E0E7C3AAE79626A70BC555639453B46E0E16EF86BF008708C70776FEB8CD1FDF0 + 7DA32A30295E00D206532FB9C5CD2F35BC1AC86B6D343A305324FF4AB829364E1DEF18 + 7675020EB6B44224C5118471CEB5AC989EED3FCCF2798B5CAC34AB1F2AAB8DA0693866 + 464051A5740756C10A1656910FEE1B166CC907EB1561855F6780064AEAC600E4E88A6D + 58A79B510C15335A378BB994C442EF8785A16ABA883B47781345A75E4E27AC842D9075 + 6D5AE4BDEADA18A3884878C109905A8CAC21E236F17A707CA132D33CD103B25F933823 + #)(p #00C291D6A0559549123A39E290CD9CA9EB897F7C8B45EC30AEC9D37C866DA57E + F03B45215DF88312BAACD0CFA1DCF7472EA9BCEF23B9F5961F84100441AF58DD37DD96 + 8CA131B94A3BCD54904EB4859A8F5BE4A7AD56B067DA02F1A8687B366FCBF56BD6D458 + 4787F4E0873B4962E3B358D1D282833285AF017445D03737872828B0D2DC95965BAE8C + 524EEFC24A36EEAB62883BCBCEE46A26C993112B170DD024A0AF46867D9C72156125EC + A756F1DE50FD8FEECA14DF60B46C7E00AD3883166B#)(q + #00E0C275EEFCC37BAE56DECC8BD107AF89E3C3A680DFADBAFE68880788F5CE92C014 + 5EA33C4555D7AB87FF3E258EB24BA1A399F37C2F595D5891A53D47D7AE91D86DAE954D + 889F995996CCDC28F2F3CE9B6449CA3EB779952D46D9D02A85C1DA629C240AB97F7D75 + 89FDE7DE7C72C08624B5B9641F7503A4B76E87550D6E8441AE8FABC74CB4AC9A060A42 + 54AADE896D072B5B0BFC8BDD567095405CC4D311094D17500B0455DCD3D1A4D5454999 + ECC67787832239920AB7046EFF00833977F7CF#)(u + #608A137207BEAD80E6284FE4C18AD6DDAD8DC2B43D8B7B104037C31E09C644CF8974 + 1ACA4931E3FA78DA331570024CB98FFE56264A88F1BBBA94B7AB7B3D0EE29FB5CDFD09 + 7242DC808C74EB93044DCAE557C17B6AE607710AC6915BDB2AEE85377BE9300253AC05 + FCA6C471BC10AE8824F671B7CD9C171243811D489A87B62927A37FDB2B551F12A7C92B + E1A6EC5DB44F5AB12E9722EFD7AF9CFB43F82800128722147ACCE75CA0D07EA7189E65 + 096AF183D8171CF4FCB3A7701DE186003A77#))) diff --git a/git-branchless-lib/tests/test_data/gpg/private-keys-v1.d/E54F2BB69A7933D919F95FEFE7C785EF3B2D3624.key b/git-branchless-lib/tests/test_data/gpg/private-keys-v1.d/E54F2BB69A7933D919F95FEFE7C785EF3B2D3624.key new file mode 100644 index 000000000..7eb756466 --- /dev/null +++ b/git-branchless-lib/tests/test_data/gpg/private-keys-v1.d/E54F2BB69A7933D919F95FEFE7C785EF3B2D3624.key @@ -0,0 +1,42 @@ +Created: 20250419T021459 +Key: (private-key (rsa (n #00BF93E1788CB4D8FEAC6FA907E1C29321C0C71101A3 + 4F4191FAF85319472C61386B47179FC5AFFE021E8B4A7883D04C24229DC97D3C675CB2 + 827D0F6F605E8727D3DAE59FFB6464BD5F3A87C7B23E453CD38EE93FA110905232F1F7 + A5899D93437BBE077EA7F2EDCBF4DF862CE5FE969F34B7ECD7639BEFBF701C837A7C1A + DB314AC31B488B6392E44476E279483721FE8BEE173957E7732A88A604A62B65BC071C + B3E295E4D69D60068C206EE516B0667894FB45F5742BDF5D557C1EC2DC020951E8B293 + 6B112831613338D5D14DE80391F5572DDC9895CC74B0C869B70A9DEDA6A521105EDE87 + 799CAC2529B9D96EDC15BAFB65D37DEE4051CD84CFCCE3F180BFA18FFC1ED3F7F87098 + 1E973AD115DF98C4790CFA78FD98675CBAE25160390E9928A6CE93E8A39B618FA6F3DB + 0ED0388D14C1ED6AD9BFE79DE71BE4AFF3F828840DAF7307C45EC9B19E34F014C83E30 + 2246AE1697AFD3B83DDD60A7A11137F6AEB2A06F32BF0A84B3C1978B771B6C4F712973 + 0BC271989CCEEAB5DAB9E27B9F#)(e #010001#)(d + #0E62DFE7D3C458166969BD2B255AB6FE2E1538038412FBC35A6526405BE617DE17BE + A3261E25AB5DA42D252EB26C1E220956A2CB6FC4C749746C48F6A45C94DD4763AB557D + 9853CAB722FB107C23E6F5EFC781601DA53A2E086E8C66DD541621F0FB81FB5F7B506F + BDDE5A6876BA99BBC9AD155768254D03103B971E615B0A225DAAAD81394E6B85A85751 + 1FAA079EA7B1CDF380E97A602A341D28416B0610246C9529915E06789357A4202F607B + 7918E26D9C0685E0BC06AE2EFDCBB8C37F8451F9D65B9D8024CB5A531664826287E46B + D2FE4027F6FFABE13BBD021D9D00ADC4ABBEA4C3718777DB28065F3073547C67B8A503 + 4B09B6ED45FA62656071306D5410253CA5CDCAC6EF73DCD474C4C6D3A267509C05DD8D + 0AA64AB995D049CF3B761357FC9919F0B4F5BF177383E69D68BB1CD19C46BDD0848268 + 2FD0404A44CEB8BED6501180E5527CFAB12AA1AE140F3C55D69C3E5DEC9944DD0FD9B1 + A744F60ADB0AAECB9B752BABE3A0B50270B2B58EA59A00EA890083DFFE80622B589A59 + #)(p #00D6D1E0B8F5B05C58275963029C341BFE6FBDFBD179AD08F2E12AEE362D03AE + DEFE08C4D421572F4F015D14B63CF5AF9301E6871E57679BDB1E24E24E2D7EA3EE23B7 + 1F0FA8390BDF21A7EB595A7DD3B4CD7D932A737365089AB3197E6B8560C01CBDF957EB + B8906783709C12E9CC81BE661A68C00545F6AE0F6DE620D6F57599C45E5B551D318705 + 44B609535862576187E76F859A9FD2BF036386F4DA5EBBCD62A1C81CB88C72FC7C800E + 8291DD284E23F14BB477EAE94BA884A2EE53766A05#)(q + #00E44D691B94E729DC3F4A354F15662A54A6FC4AC5163CEA17C0B7046FD8EE8B0A02 + C8672745CE1A7EB7D21C6CC4187F1955D12639854097E1976CFB72C10590E5F7CBC97D + 49F6243AD10E35C8CE26062B5AB1435D2BC0C0086147501C4BFEE30F67596A3526D555 + BA61F91C7DD491D5FF1D163689FAFCFE76B665B3CAA56AE641594E4559473014A64FF1 + C93ADB626E1167895707FEEFD3D290CC3AE94E5F298A7C756DFEFBF550F29EC41EEAAA + 5FD72BB571B6BF69B85E9A3005F24355F46C53#)(u + #008E66C1867B0618E613DA279DBC57228E8C3F570EFD0226C92EC1D2E1A9D910CB92 + 304985287FDF65FB2BCDA34B0722A692346CBAD4D0BDCAF47C1B5162775094D13E04D5 + 8A22C0CB7A5AA7E9B6E4521FACF6B269D2CB458492F8B189E6511061DBD73B93B3094F + 3EB01879AB7FD3DF7ED6E575DFDF86492FECC233BC7D53B8D4BFEFF5129FCAF71CFB6A + B27B2F8BFBBEB3E684988CD756780BBA373866BE8F253F644FC36393E9E08A5BA5FD0E + AA9518651DEDA352A534531C6BF71E694ECB42#))) diff --git a/git-branchless-lib/tests/test_data/gpg/pubring.kbx b/git-branchless-lib/tests/test_data/gpg/pubring.kbx new file mode 100644 index 0000000000000000000000000000000000000000..5b0e965752e9b3a6f22f5446066d908c92940f0c GIT binary patch literal 1980 zcmah|2{ha37SBIID)uc(l%T{?MRXKRDMo3~T3V&8kP?hFw%STjYqgZ9WiV-~lG56O zTE;ST3uBL}ofs+|Rr{cpKJ?9b@4WNQnS0Ln-QWH0J@Icg^jKB>s8&8jL}Y&gc`zkp7@kVa z?3@z2m;KfoB~`F%taZ@C9Q5`B0P%uAVCj8?KCHnY$YK7UYySsI2T!@c1IRv>2Y5jx zO!w6@Cw%gNY;WR9G0gE|M_#XIafvqIiQ$CVbtp9PIcJ0wm1JOR6R6g#{8Qe>w!7(G z(L3Liw;b0!gT|~#PSJg9d~PJAxd&4!%={8DBSZ#ESV2Nk54#qz@{Y0=IzXt|uJ|0Qt1F8O|49k zUW71ZBqnITJVyw{`w+beCtdt}^#8p9{GIS*pc^6rz(6{1S;+rW6({(BeK3N9lZX2V z6wDmPHN`{la5`Qoz;gQaY$ z!2q(G5ks;#HEe$T-%{P-oqBF)dc>Gdcey=3-;&oZaN)F z3YSwzKq*jp=g?A%0UERfRRuP~;s~D%gRWswr>$!aWAoLr^1A*bjmr6MVr=(`lcxWuOV8wKsYP74g~_L z|BrE7NUYHN)kAwP{EDHhHj?ab?}UKDONQxRH*F%J;z1DcI1vy%xvi z3fdzv?le}R_m-vpZZ-QZ_elnm4KD0qEZxkgLkc=amp;5Gz zh2PgE6Hd(TWsy(Se*WN+x7b3Ix*L95a#R!h3SpMwk}>n+t?4kc)3SRhU&M8<&IPI@ zQ6W@SLL*eFYWmU4V1YB&eWdR!yuvLsbK7J+NOjWj%5CYkF)+6^n@;i)Qqsh0>Adf= zWOJmiUsW5+dDIzH!T6~bR!Bo zlC~qw+}I@MNN4NyiB0Cb4&(hAx|`#EqhZ?GS(iUoiP}wK7v|y9sH>y=y*dv>TNgZs zTjmPp5Hsbgn@Vwf<$=)Gxc16lPA!Qt^flzpm%+2knRRE!ooN)I(|?uGAN!$OU~yHg z*(o6i?@Rv5fjn*gIr-gx){N9oN01MVn+&{!9Njk>@*j-G``?E0QQ_IsFnmt>&BEwP zQBsJw+jdBbHV@5V7IQ7Ys-jl)>U&mOk)rmUzKR%0i(t*&ByK7d7kkIE>=K^dua8~k zlnrdgh07?h0nR+fvP4&S?NP&cMTE9A!`F3Kx47jdYanhwSN&_FVj3eVZH9LLY4MTk zEFs@5p#*yHw&6fLz0r*ROr?HTPhI%i7dB=f%ipf!$?I|3kN(Sp!Bj$fdhx7RaBTmN zy`6?f)zHPVZ`?bBXYg?u?vj?gt*K`og|t9hsom;~(xj7|qF09a?q2SKd~9a?yxoOY zZ%eQ-u`jdrdh7(;kIG!NnRhiuMc0e=XD;`g4VmK@E$m>WXAPxwvh0?1Xy}eD$G$uP zqN$`74y6H07E#OUl*nW`efK#ElSiysV^v%_43wp?pIQLF-*kt z?yK?Y6dPidL2~9DB&UQ}pp~TpL9I@JaMsP1YLv@4$30Awzm#kJ?0ABNozRO9oyU9v mlRxb8)(j%UmTkBHHk3uE*rOdp?x?1u!Qe`CN#A*90Q_G?ePm() literal 0 HcmV?d00001 diff --git a/git-branchless-lib/tests/test_data/gpg/pubring.kbx~ b/git-branchless-lib/tests/test_data/gpg/pubring.kbx~ new file mode 100644 index 0000000000000000000000000000000000000000..0736c4700d858e8b91ac2a657f79fcecb9994244 GIT binary patch literal 32 ecmZQzU{GLWWMJ}kib!Jsf(&MMF%Sl^K>z?RM*@HV literal 0 HcmV?d00001 diff --git a/git-branchless-lib/tests/test_data/gpg/trustdb.gpg b/git-branchless-lib/tests/test_data/gpg/trustdb.gpg new file mode 100644 index 0000000000000000000000000000000000000000..dae832ff32bcc28afce4202fcab725b9a7b1d9e9 GIT binary patch literal 1280 zcmZQfFGy!*W@Ke#VqnN%W;bTQ4j8$xi(`n6s>28pu)t`zjD`y+1V%(UkVC{Zwf)P( z@L_L}b7H@K{Pt>g+08p|8_!uNEX)S6P#$3~FT;zb$q6o|!Jqp4yC)i*TKD9^^?zkh IbqHk)0G>P;SpWb4 literal 0 HcmV?d00001 diff --git a/git-branchless-lib/tests/test_data/ssh/id_ed25519 b/git-branchless-lib/tests/test_data/ssh/id_ed25519 new file mode 100755 index 000000000..4053a7806 --- /dev/null +++ b/git-branchless-lib/tests/test_data/ssh/id_ed25519 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDBoAEzmmYQ3yh6hviRueloFVbZ4nFCJGiv0JMtxvD4vAAAAJi6ryHluq8h +5QAAAAtzc2gtZWQyNTUxOQAAACDBoAEzmmYQ3yh6hviRueloFVbZ4nFCJGiv0JMtxvD4vA +AAAEDRLouz+c2Gj4/SHjq1t+epV02j7Q2iOTRrPgemjfmCVsGgATOaZhDfKHqG+JG56WgV +VtnicUIkaK/Qky3G8Pi8AAAAEHRlc3RAZXhhbXBsZS5jb20BAgMEBQ== +-----END OPENSSH PRIVATE KEY----- diff --git a/git-branchless-lib/tests/test_data/ssh/id_ed25519.pub b/git-branchless-lib/tests/test_data/ssh/id_ed25519.pub new file mode 100644 index 000000000..ed16ac0d5 --- /dev/null +++ b/git-branchless-lib/tests/test_data/ssh/id_ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMGgATOaZhDfKHqG+JG56WgVVtnicUIkaK/Qky3G8Pi8 test@example.com diff --git a/git-branchless-lib/tests/test_data/ssh/ssh-allowed-signers b/git-branchless-lib/tests/test_data/ssh/ssh-allowed-signers new file mode 100644 index 000000000..7ddf373af --- /dev/null +++ b/git-branchless-lib/tests/test_data/ssh/ssh-allowed-signers @@ -0,0 +1 @@ +test@example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMGgATOaZhDfKHqG+JG56WgVVtnicUIkaK/Qky3G8Pi8 test@example.com diff --git a/git-branchless-lib/tests/test_data/x509/ca.crt b/git-branchless-lib/tests/test_data/x509/ca.crt new file mode 100644 index 000000000..0ec76a501 --- /dev/null +++ b/git-branchless-lib/tests/test_data/x509/ca.crt @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFDzCCAvegAwIBAgIURmyjpAxVgpI7cmjTvRBkMHvMHWkwDQYJKoZIhvcNAQEL +BQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wIBcNMjUwNDE5MTYzNzI0WhgPMjEy +NTAzMjYxNjM3MjRaMBYxFDASBgNVBAMMC2V4YW1wbGUuY29tMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAuwV7YvEJrHX0RDAr4OnPcds07ITR+NQEBc8G +xdmTgAYmoFDr7VtYNz90KOtxMfGgjOywgq24v0TszNC7soVpi2Pha6eEV3kO/Myy +L7POkgThiA1bPbYmHes3VjykhDrPTnaqyw02JuKL05S3/ydnhY4WcqyPcyBzhNMg +N2rXhMJY0C02mmEBVENV/6PkcKTiAswZm/5ffgC0bUQ0XvKLVryv73z/QW5HKzA7 +NpBpVu3BgYDQ6Vg2QljHC93HbPQqsQIn4k0u49YZSkKsESaWq/W55w8/EG+1Rj+R +qSrLul213jXXT9UMEqrOOamJFR/EiXyCL8IyvhJoY8eX4V3p0oK216mlwQo3z5fs +TaG+bjEpeKUG3Rz7akZAs9+KRnRzsLDVPjoFoKYSRjbnrZEIqQJnKdhcD8J8tQHW +VPD9YuoiMV+FIuALaagcpVeI+IC5sjrIlLIedQtEOA/VVchb2r56rIl6g31c6EMg +MDRCqHELpppEp56o//vGkkJI725s+aBqKdj/YWq/YzjxH0AvrIb7JUgXyUKkwdaN +CARRr24AgZs/o8us8/pZqXX7DuukhbBrS0y9LsX/GA5fWJzIkqOSfy9csH1l714g +nrznikU9SYvf9f0qygV+aGGVrLW51Q0pnvLJQOEy5DzHx5J0pI5GB2jWuXUajo1h +4MwKwSsCAwEAAaNTMFEwHQYDVR0OBBYEFJYwGDAFxEM0DUz38OFoVkNN25k/MB8G +A1UdIwQYMBaAFJYwGDAFxEM0DUz38OFoVkNN25k/MA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQELBQADggIBADcdwqgWUi8GsWptQGOnILV+IVgSOGm3KS77kfay +ERko8z1+uWV0EIEB1Sp2m6pPlETrenJEt2wsZ3Xgyzp9WUf8p2Yn8J9lVSY7rLY/ +2NSb0k563sBSariK8zqC46C3g5ORXRz6D1/QmxA4HIQGN7Jp/oXDzg3MBd9i6qYR +VUAGdCjiHsIpQsUppb+DrbEIOwRBFoY3C2wVs4Bl5cP12XjMXz4qITIv2GvX7xR1 +og4IEIPjGPP+VPI+hpv0Nekd51kJfUWBw1l2uORUZbYkGmRzVb8NGjvCOKJomjIw +I4nVSrY83AdjSYABBfpQ7hVZhja5Tsq/kNdjl421f1WJRjrMjRFtg55FBLOZ/n6N +X7jrgaoNhizpFpUQ62ODGB9vA63olzaHNk4NHKfZ08mQtTT6njd51bvOmeZYo1i0 +5yI6RiX8r9LgAXQvg++grhJABT1apby9K0Tec12FnGmeM/O6A+xAvwyQEDRC+zaP +HNG4Php7rtQMB7yFUXIMcfEkwcYBbzgOdww0211UR+Z+AGZRPu/VZ9aBuMmOM29+ +46avaRhQXCPsHBW3+y8T9HEJHX1u27R87OvvPcFxbI8GhpasFN5Z21a3xmooLR14 +nBqWi6/abhXCoIvJYP4nGzxRINpCULUFB6q+bl0hbvsJLiTu6Q6Ry6xRtJTkzzvA +h/cj +-----END CERTIFICATE----- diff --git a/git-branchless-lib/tests/test_data/x509/ca.key b/git-branchless-lib/tests/test_data/x509/ca.key new file mode 100644 index 000000000..d26ee54dc --- /dev/null +++ b/git-branchless-lib/tests/test_data/x509/ca.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/git-branchless-lib/tests/test_data/x509/ca.p12 b/git-branchless-lib/tests/test_data/x509/ca.p12 new file mode 100644 index 0000000000000000000000000000000000000000..2a58a8f492b9565ec6316ca6eff0085098b15bb7 GIT binary patch literal 4104 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 literal 0 HcmV?d00001 diff --git a/git-branchless-lib/tests/test_data/x509/generate.sh b/git-branchless-lib/tests/test_data/x509/generate.sh new file mode 100755 index 000000000..ed760469f --- /dev/null +++ b/git-branchless-lib/tests/test_data/x509/generate.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# Adapted from: +# https://gitlab.com/gitlab-org/gitlab-development-kit/-/blob/d2087a9b973b1b8c61a4787aafd4f2ae3b968d54/doc/howto/x509_self_signed_commits.md +# +# Requires openssl 1.1, 3.X does not work + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +openssl genrsa -out ca.key 4096 + +openssl req \ + -new \ + -x509 \ + -subj "/CN=example.com" \ + -days 36500 \ + -key ca.key \ + -out ca.crt + +openssl genrsa -out git.key 4096 + +openssl req \ + -new \ + -subj "/CN=example.com" \ + -key git.key \ + -out git.csr + +openssl x509 -req -days 36500 -in git.csr -CA ca.crt -CAkey ca.key -extfile <( + echo "subjectAltName = email:test@example.com,email:test2@example.com" + echo "keyUsage = critical,digitalSignature" + echo "subjectKeyIdentifier = hash" + echo "authorityKeyIdentifier = keyid" + echo "crlDistributionPoints=DNS:example.com,URI:http://example.com/crl.pem" +) -set_serial 1 -out git.crt + +openssl pkcs12 -export -inkey git.key -in git.crt -name test -out git.p12 + +openssl pkcs12 -export -inkey ca.key -in ca.crt -name test2 -out ca.p12 + +HOME=$SCRIPT_DIR +GNUPGHOME=$SCRIPT_DIR + +gpgsm --import ca.p12 +gpgsm --import git.p12 + +echo disable-crl-checks:0:1 | gpgconf --change-options gpgsm +gpgsm --list-keys | grep 'sha1 fpr' | awk -F 'sha1 fpr: ' '{ print $2 " S relax" }' >> ~/trustlist.txt + diff --git a/git-branchless-lib/tests/test_data/x509/git.crt b/git-branchless-lib/tests/test_data/x509/git.crt new file mode 100644 index 000000000..7f763ca25 --- /dev/null +++ b/git-branchless-lib/tests/test_data/x509/git.crt @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFbTCCA1WgAwIBAgIBATANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtleGFt +cGxlLmNvbTAgFw0yNTA0MTkxNjM3MjZaGA8yMTI1MDMyNjE2MzcyNlowFjEUMBIG +A1UEAwwLZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQC0xJXTbr11aGKNIJU8NzV4249b4m9hv4Y6n4clycYQCiBI4ATefcfrMWm4IMW7 +l6MkUEAONCfS0SXkRpKLQGxQT3y3zJKJMJITEym2kStITAjW5YaUkBh3EL5NrM8W +gefYcD5hImb3ARd5Yy3qkZ0OqnllSg3mJXtmbDSstFVqZjv3K+05w5D7l6nITT/v +Gfsq1E424rWMV/bRRACy5hW3VmbvULpkAZu8GHCEYDFveTZznw40UrMJCH8XgnyK +H5ynLGSpKck3KvSMtAR0pisNk2H7HqCgZZNTfhM9LxRV6Xyt238CqTy9xByQ9g+h +eBMqSTR3UgNw4ChaoyperRt5UcGmuWPuLUebb0JM1d1Bj6c7eOpDk+/6E29rqVEk +xBAsDHQBV01SCjo9F4sFp2kVLrBnNrF2NlTl+62TC3l098MGTkl6Ok3ifMZw22UC +QCkEa6CLqSWAJEnp34Xd1lxbi16Qc7vIL11gTWe80cGrlGBsfzqC87L8yjtRduFA +Mbq8chYGVHgOjMqjBsdOk/5JF3zkoZZ2KNFqiMXSMmSR0i51XYt3m/YTQnkxw1vM +fJH2i6A3QfeAmzFkNPiOXkfIb7teTPC68DJs+zLEx2be2M+Lc9+eAQohBNtiNzbI +CZiTy2/JuTHUQnGCDAfnzGOwLbU3kXicWyq7Rp5qz0ZiZQIDAQABo4HDMIHAMC4G +A1UdEQQnMCWBEHRlc3RAZXhhbXBsZS5jb22BEXRlc3QyQGV4YW1wbGUuY29tMA4G +A1UdDwEB/wQEAwIHgDAdBgNVHQ4EFgQUip2EaqGZGmRyko5NT+/qKTkFIc8wHwYD +VR0jBBgwFoAUljAYMAXEQzQNTPfw4WhWQ03bmT8wPgYDVR0fBDcwNTARoA+gDYIL +ZXhhbXBsZS5jb20wIKAeoByGGmh0dHA6Ly9leGFtcGxlLmNvbS9jcmwucGVtMA0G +CSqGSIb3DQEBCwUAA4ICAQCll+BthHYWOxvVJRabWCjIQCzRyAEtIpP+R9YL8uUE +Y+s8E3chxJt6ZAYO3Q4cpKrML6hJ9jYRm5ZqS8y9Kk89lltGHm58ZZgbTkUyn5v/ +OamO5gtjzKeFkfsrOUpvQ8voYnuXLZxoI+D3mR3Bi1ikSwWfidtC+Oz7chHg5VPK +y7hxlXJANdj5Q6XRMaDAs3tgEvLSYPLKL9ipwAu7oLnAO+t7hqHk1bTH7s4ljrne +7Njdax8sZjVnmOtWGfyv1PghMd0n2xadYRK82v/RxBIk94wqOhF2jeEfWpKafTh2 +F/yYsuTK8d1xd7L3+sy1uqcgyO3GMZKGdUc8txzIDfICBDTk6eALgum1dq8BRVLH +sTwhjv5QjDHkP6RLFe4ChNPnC2AKGAKZxvy5RXfmYDsITlPFiOy8sunVHC+/2cK5 +lTJcitZ2fAmJFn0rWEeNBUa9Vy5VIEuPFEJ2v0C47Y4IWA8Oyjuq1Fme1OOu+19O +jnwhJ80SIJs6dHUpXUkWUJEBHam8bnRDZmc/H0Ytz7TUKDsAg4Ix6Jnq5w8gEOVT +RpG96B3DQT5oDXzb8ozHarKE7C4jt0nPUw31xnUGRmF2HVX4eZhncue63UiVlzNw +WYPCdJ15c3Dn+FKksYRDOHhh9kbaXlm9fXbp6s9xRw7e2dW9ymn2CpdSez9CPeIc +7A== +-----END CERTIFICATE----- diff --git a/git-branchless-lib/tests/test_data/x509/git.csr b/git-branchless-lib/tests/test_data/x509/git.csr new file mode 100644 index 000000000..f46563132 --- /dev/null +++ b/git-branchless-lib/tests/test_data/x509/git.csr @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIEWzCCAkMCAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3 +DQEBAQUAA4ICDwAwggIKAoICAQC0xJXTbr11aGKNIJU8NzV4249b4m9hv4Y6n4cl +ycYQCiBI4ATefcfrMWm4IMW7l6MkUEAONCfS0SXkRpKLQGxQT3y3zJKJMJITEym2 +kStITAjW5YaUkBh3EL5NrM8WgefYcD5hImb3ARd5Yy3qkZ0OqnllSg3mJXtmbDSs +tFVqZjv3K+05w5D7l6nITT/vGfsq1E424rWMV/bRRACy5hW3VmbvULpkAZu8GHCE +YDFveTZznw40UrMJCH8XgnyKH5ynLGSpKck3KvSMtAR0pisNk2H7HqCgZZNTfhM9 +LxRV6Xyt238CqTy9xByQ9g+heBMqSTR3UgNw4ChaoyperRt5UcGmuWPuLUebb0JM +1d1Bj6c7eOpDk+/6E29rqVEkxBAsDHQBV01SCjo9F4sFp2kVLrBnNrF2NlTl+62T +C3l098MGTkl6Ok3ifMZw22UCQCkEa6CLqSWAJEnp34Xd1lxbi16Qc7vIL11gTWe8 +0cGrlGBsfzqC87L8yjtRduFAMbq8chYGVHgOjMqjBsdOk/5JF3zkoZZ2KNFqiMXS +MmSR0i51XYt3m/YTQnkxw1vMfJH2i6A3QfeAmzFkNPiOXkfIb7teTPC68DJs+zLE +x2be2M+Lc9+eAQohBNtiNzbICZiTy2/JuTHUQnGCDAfnzGOwLbU3kXicWyq7Rp5q +z0ZiZQIDAQABoAAwDQYJKoZIhvcNAQELBQADggIBACgq1efujzV1AMBDVG/+EJYG +zWVLOEXn0PlSKWnhzqvsP8E6AaQZepbrA8x87NB9cTs3e+mZg9olK1m3Nqmq7edf +rkWYnrxQzFaCqGjWhu4EqdIEdQaQqdjomNgTjSezInssFtZTn+cffOOj9AWqAuRW +FWQs2+oCtv+VTn5We9vLvOGLQ4QvZ+LFqOGjcLkzF9ck+T7ms+Fehju87FDPiI6t +Ol5b4XFth+f6td1FSDZgE/EGl/pH0o8A8YGV0riKJtjBTx0WdpmXBiVxMe3GpzGe +W14kbaXEMQQY1KQT3MeUoHIU6lM1dZFg+IULbYRFG/k6h+/iadBdPGRUWtEwt4hQ +Y3EwV+HaTP/KYywMYs70asD+ib91wJCOgY3WkSakaqi/JqLq4XKYX6dEwVuvls0T ++RAOLTaTsYghojUAcuz3ej+wV6pb5lSHGdiOlPVEnUassHMhfq2fgNnq+V7mti5A +aqAFWmCP06cOqjZKs3V6pwK0OM2u64F9F35QcwtqGxm2C3b9icZ0yP7Qt2FYfL/R +S2EbmzbZosXsxAWQ2RAM15q/PyiKJu6TxTaQ6FSisna7PgGEjuo46LwTeB084Wk6 +rerb2oZ0bpGEhF39LxwFdJnG1f59xXcWsO+iMTWtNXcxw3CXp6PnmTWj6lavh6ig +gHQT9wM64wc/2g0Jm8mI +-----END CERTIFICATE REQUEST----- diff --git a/git-branchless-lib/tests/test_data/x509/git.key b/git-branchless-lib/tests/test_data/x509/git.key new file mode 100644 index 000000000..a105e0ddc --- /dev/null +++ b/git-branchless-lib/tests/test_data/x509/git.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/git-branchless-lib/tests/test_data/x509/git.p12 b/git-branchless-lib/tests/test_data/x509/git.p12 new file mode 100644 index 0000000000000000000000000000000000000000..8dfc614b7ff87fa4b38efe5aaecab2b25b8cc9b1 GIT binary patch literal 4198 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

wR&s)k3A z7;t=N59qVsvaRoR|c zUTGYACrwvLDAXxU+O z{4`|2WNu{l$%3KR{smwd2L-z;Rt0Cj#|5!O3DCwlp6J{(Luf!@(5p~dDoSxiav%>S o83lVU?%PS3vUMk=hof7Kc8Q(|qH%w(IfMG_fBcC$3iXl&Eapdw_7L}ZuU*s{b}ZW`MVswqv^7MJWH zBum!$B|^%Nkdi2caGNf_QT=++>)zKruh;pW=RD7ImOnn{{d@rc00IaE0FIKZTmanW z#?$Av(Kqbx4bsfRjdR;|001He00N}JkBZeO^D%FZTc}m`7rYcmt$VoeJ(v64PWH^n z6{OJ8wEW$kHVQkT!q_tI01!Zs8;j(2_>auQpEeKx{^9#MaBpyFW1?@W;~e7P1cCncSQHG(rxI@zzXAgS1t0*F1bRD46v{&Z^9TxT+82e` z0mB?XVbKTCxcztx_OzrB2F-Qs$KZZA{&6J@v}f}nKnQ?`1`+~r@5K+Ifq;N|NZ7eg ze8quF1}N3>nO?sU>|AW$>Hrwh3w`uFJqo&yWj6cX#tN@TRhsofe_|!hmC;ISn+)dI zPwFe;-06-J=L=#_g~GqGE0C3s)4&s#U^Y7U_Q}lRPip7J5_(O8ih5wUeUtR3S8CQ3 zUE`7^{EJfp5CO4I5qOVhu`O0l)N$7xfR_4{^_(f++({67Cv)v=B%sR60Bg5EKiN<^ zAHIIb+fWsyg-dlm`MxdA~1j%3v#Fxug{q*4x2oHWsRHMH{xsaRxR<{zKyriJu` zeX7ZNj6#*3`?l3%2c8`t6cjCbe6TPtr?8ut1o$06Ddsdj&%#ZKQ znlrimz#FX`k_R1?=6H~al`k)osR3nWgSrF=D_@k1n=WDS7J^)rhtCMLgjWNHEI
5iX3 z{&j^tk6_B4{wytn6{EDz=3bq9|oN6k;NL}YP1N5N=E7b&-SD2W$rUg`~qX6ohi z6fb_UE)3+rXLIAq&L2J2sPSlB5`Na|MrT@1+C}7`!j!WYx`-2)DeaDqG-_@#d7Il%U7%EQlEXNgzgZQ;eLKLM6DV_(3|DtD zaIge;dnQT}1!>dA!j7$coNzm-Z!&UC4<)-1-V2sQNksj9g3v~S7!bG)hM;)3!)DoW zwat|s5P+9yxg%kLgqC}F5giK<)sb>mqMGhC${HNTa)roFrA3{{I%leIG;mNQh*@;} zioxs!e}fuNHP^uL9zuk*;nxBe#gDg~Df_gF@97N>F#Am}j6JmQsJ*;MpuJ+%#H2H9 zrN0QBeZmyhl#YesE8N%OS|7vMkeBD)<%>{=P^!}8?iOXzBjvoNn38f{E$|_Uc)Wn8 zcx9CH>( zKqj!0M7(0M4}&YOtwkoCt(}c7g2k)NNMs1lI>t!K`tX#z&BFbLGlfYPJb&7iT8;gZ zjSn5He|+tYRgP8F^d16P;cIFCIFO2rnP=Syg=QT8{8jJ89>`wRMgl5cJY`e?#~1+gQxmWFR40WRjc^MkHK(Y0O4 z`+Xu`<(Ilknw{A@CoNvXL5eN;@ySGZk5q-v&CcsI`+257<1>r5k6DkLtZDa9QkMz2 zA(crlec>(M!lHNCuPN@*Hb=Z5nN>r!6+Q5_mGkECX~=(=firrF&8x0V^=du*ZDnuF zlO@5Z>V2NK?(qg6i;L=S&0iV?0HDS{0N%z0xZeiAopN>V+RaNUSQ*Ol#oDhL?WgGK z|70(>8i2piI)>}|Kdl*?>|0!0^hbI*cNF_(|2@T1Hey@3y>+Hdbq=5sH^ErN@OkSpn_{byR=HxCt)`bv^=nqY&oei7l?8 zc(`b-sJ=hgn###4>@?Av-^o!KFvU$)C!SjFGXPY)5wAJvGH-U@3CL`a^o_Mg`-I{G zZo#n@m3+JxchJHw%ibtZb1GEs!mBJLR)MMcs<3ni&TbaVIsHVWm=1C~WhT62V`}o(y_tOLn#GXA@B6l=f8;g zoG&z&ZxdD%qykTwSnv~ccF-XO?&2C{uDJ3boaJjyNxDENb)^+*YJ7oUG8x|PJK_u? zDud6n=!FVV^2ReS<3@+h*wF1#1L`}Gw)Q5j4SmgjT(S4ONT4lNeC^gU51JsN?>G2M zKrKVyiQPHS4%75C;~n8sw=;v3`aCW@>c==S`ZWS=>A}opF;XbH)rK9;Sf;b^hgPDP zXeaDyvYlb4Prco-kM}=fJUN)Q4wtdvUV6aGY#_fJc;p-&*U5J^y~n4k4n08fqX}-C zW;>RtSK}EWH*8eu$=M#gv8*6YJHtR^*!lp zhC>1iufdM9+G4?SZOjW!Q1~cZI=6_8ykoqK6Jch09A!7E9M{RTA@BAMcfPvI^f2ZY zbNygp@*4q1c0nA2qk8a&kABbFb75KPH{AA)uUwO9rd#D6h1^OQA+64F{6)rJpXlzX z^~>-l9vJ>pKd%qXdRQ4|FS^igztD{wE_^6Z&#HTI3jV=LA2q;dS9EU{7RwZ)Sk?w>ofWX+PnQ;NyOm$Ex@UTTkxwc&Lnpq-xN-y`Q z_y$e7#jNRwRh7(2;tW!$c)CfGOdSn-C*G(3yB>b6|r*~duxn) zPryF62SUjXK{Axp(5tTg)AvV>GP3sjTF10dZ-xf=POn-95)k3Ao3>~>$~&$0;9Bk4>^&(S+=4t?%4s*Q*L`O)ml>@)+jsPaEqh3B3C literal 0 HcmV?d00001 diff --git a/git-branchless-lib/tests/test_data/x509/pubring.kbx~ b/git-branchless-lib/tests/test_data/x509/pubring.kbx~ new file mode 100644 index 0000000000000000000000000000000000000000..fffe9b786846594d45b8781aaeeee67a5eafc2c6 GIT binary patch literal 1505 zcmZQzU{GLWWMJ@iib!Jsf(+)186XT|LjdbRW}rL}%wk|*6=q;$kYPExMkn-r^E3Y- zoz0h)-{+pN^S~#FS|BOnma}*XPiWI5>!OUydj(Pqs?W$~GB7dl0Ojg{m=CJ52hL_> zV1bIkDWD5FKpba3+tiB0+=84`z2y8{RGvW-E5AV#)At3;OpHuSSgkeSW#iOp^Jx3d z%gD&h%3vU7C}JSQ#vIDR%)^aju7QF$uaT*NiJ_&TnX$Q%Nt6V?ks(mP*vJejfKz7^ zlM=EejI0dIO-%d@KzDI5H8C+V>}IV_`pCJa^oxsu_Jfz_3vZjeX}S303JdFbwxc&E zH?XNK2zdQAI>Ow(MB{a#;l~9%Z#Fco-Lc>0&6x|kH??MVCqK+y-V$EP_vg$e{mthl zu{`YHjkevUCi~hv%w|c8)p@_NRi}B))E;$Tp0fSFdU|W0SkapPVuj+C%L?XM*IN!n zT+lU}mB<+49QuFplY%9Wn9fMf{uf`zuqD^UB<@pp*q-(8YyLatxoaC(n@z|Jdwa04 z;lj%ZGpC5-+;@-Xe9_v-r2fcP@9{NBFQ+wvYSUJK-T9o~ULb#~oBhO2G`&?#naOtXRr+ zSLSz?o5SY&U2Y}C8#Y|EvtnJaOvugb`PzvbE1A+YZ^ZB)s@ckTE#$-Bq*qFY@vTY^ zxHDJCEDi7Y(Xexq)rl#ao0+{ zD{^J&Z@$+{S~p~S`|Q;_`d@-CK4Q*^NsA}d>&I-UO?@AyFmKQEE>~O6?)zW=YMo-O z%SfEMX6w$Yyqfbqopg9;^u*@)@ku31`rOzvuI((9>g!E>aE9xkHWM)KFfI-@2sDrd zraM_a7BLo)X$BGotVf(pczwQqc$g99?0b8ry@5PPTA4+{K&%0~0)CJJVMfOPEUX61 zKngk80n;`x*)cMh%N|-G7NpO%F)P<0dAY*YI>iVfi_GnsdcP-r+axHd@!7U+XKIN+ zBjZ)Ave~Qrr?|YXDstJLqmy3x;Ivh3r2C)cY3d*5r-rIoui0jQZMp!u=H=YJue?Am63 zF?}ifJd(54wefId*^Vb6soPYfQi?*~uo(+twzXINg zMz)#l^gFeG!u90oy<6);JKe0#^a|!S&vRwjJo8^&Z~TtejjMRubY6;06?mQ8EFquI zy!OR(vvxB-UYX@LFQ1&S)#TSa^UABc&&_-mu{dJOb0sS`)j#VmJzy-+Z+^dEosa{o zZPd~|d$nEe700&D$((2Wc^C5=hy6Sg1WcTMoAt|F++inGz3vJR`<~XoBA&vJDhH1- z=3DTU^O)R@4RL=~$B-6i_x@`7wZ@DPSur z`>K6;v5I-WIrUWDz2uvCdQIS#DNoK@A87xs+ eyre::Result<()> { + let git = make_git()?; + git.init_repo()?; + + let gpg_path = get_path_to_gpg()?.into_os_string().into_string().unwrap(); + git.run(&["config", "gpg.program", &gpg_path])?; + git.run(&["config", "gpg.format", "openpgp"])?; + git.run(&["config", "commit.gpgSign", "true"])?; + + // See `test_data/gpg` + git.run(&["config", "user.signingKey", "B3B9DB339CA11313"])?; + + git.write_file("abcde", "fghij")?; + git.run(&["add", "."])?; + + let gpg_home = std::env::current_dir()? + .join("tests/test_data/gpg") + .into_os_string() + .into_string() + .unwrap(); + + let run_options = GitRunOptions { + env: HashMap::from([("GNUPGHOME".to_string(), gpg_home)]), + time: 1, + expected_exit_code: 0, + ..Default::default() + }; + git.run_with_options(&["commit", "-m", "add dummy file"], &run_options)?; + + let (_verify_out, verify_err) = + git.run_with_options(&["verify-commit", "HEAD"], &run_options)?; + + assert!(verify_err.contains("gpg: Good signature from \"Testy McTestface (Test GPG key) \" [ultimate]")); + + Ok(()) + } +} + +/// Requires `ssh-keygen` to be installed and its path provided in the `TEST_SSH_KEYGEN` +/// environment variable. +#[cfg(feature = "test_ssh")] +mod ssh_tests { + use super::*; + use std::path::Path; + + #[cfg(unix)] + fn set_mode_700(path: &Path) -> eyre::Result<()> { + use std::fs; + use std::os::unix::fs::PermissionsExt; + + // Git doesn't track permissions and SSH will complain that the permissions on our test + // cert are too broad. + let mut permissions = fs::metadata(path)?.permissions(); + permissions.set_mode(0o700); + fs::set_permissions(path, permissions)?; + + Ok(()) + } + + #[cfg(not(unix))] + fn set_mode_700(_path: &Path) -> eyre::Result<()> { + Ok(()) + } + + #[test] + fn test_valid_ssh_configuration() -> eyre::Result<()> { + let git = make_git()?; + git.init_repo()?; + + let ssh_base_dir = std::env::current_dir()?.join("tests/test_data/ssh"); + let ssh_priv_key = ssh_base_dir.join("id_ed25519"); + set_mode_700(ssh_priv_key.as_path())?; + + let ssh_priv_key = ssh_priv_key.into_os_string().into_string().unwrap(); + let ssh_allowed_signers = ssh_base_dir + .join("ssh-allowed-signers") + .into_os_string() + .into_string() + .unwrap(); + + let ssh_keygen_path = get_path_to_ssh_keygen()? + .into_os_string() + .into_string() + .unwrap(); + git.run(&["config", "gpg.format", "ssh"])?; + git.run(&["config", "commit.gpgSign", "true"])?; + git.run(&["config", "gpg.ssh.allowedSignersFile", &ssh_allowed_signers])?; + git.run(&["config", "gpg.ssh.program", &ssh_keygen_path])?; + + // See `test_data/ssh` + git.run(&["config", "user.signingKey", &ssh_priv_key])?; + + git.write_file("abcde", "fghij")?; + git.run(&["add", "."])?; + + let run_options = GitRunOptions { + time: 1, + expected_exit_code: 0, + ..Default::default() + }; + git.run_with_options(&["commit", "-m", "add dummy file"], &run_options)?; + + let (_verify_out, verify_err) = + git.run_with_options(&["verify-commit", "HEAD"], &run_options)?; + + assert_eq!(verify_err, "Good \"git\" signature for test@example.com with ED25519 key SHA256:R3Yi2je27BFWoROhBpnpb3neSsy86IHXZW9PZ/nchNg\n"); + + Ok(()) + } +} + +/// Requires `gpgsm` to be installed and its path provided in the `TEST_GPGSM` environment +/// variable. +#[cfg(feature = "test_x509")] +mod x509_tests { + use super::*; + + #[test] + fn test_valid_x509_configuration() -> eyre::Result<()> { + let git = make_git()?; + git.init_repo()?; + + let gpgsm_path = get_path_to_gpgsm()?.into_os_string().into_string().unwrap(); + git.run(&["config", "gpg.format", "x509"])?; + git.run(&["config", "commit.gpgSign", "true"])?; + git.run(&["config", "gpg.x509.program", &gpgsm_path])?; + + // See `test_data/x509` + git.run(&["config", "user.signingKey", "0x33553E43"])?; + + git.write_file("abcde", "fghij")?; + git.run(&["add", "."])?; + + let x509_home = std::env::current_dir()? + .join("tests/test_data/x509") + .into_os_string() + .into_string() + .unwrap(); + + let run_options = GitRunOptions { + env: HashMap::from([("GNUPGHOME".to_string(), x509_home)]), + time: 1, + ..Default::default() + }; + git.run_with_options(&["commit", "-m", "add dummy file"], &run_options)?; + + let (_verify_out, verify_err) = + git.run_with_options(&["verify-commit", "HEAD"], &run_options)?; + + assert!(verify_err.contains( + r###" +gpgsm: Good signature from "/CN=example.com" +gpgsm: aka "test@example.com" +gpgsm: aka "test2@example.com" +"### + )); + + Ok(()) + } +}