diff --git a/.github/workflows/linux-git-devel.yml b/.github/workflows/linux-git-devel.yml index 086c7175a..828e3a56f 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 @@ -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 4650c6d26..acd8951ef 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 @@ -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/.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/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/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..3b7087a07 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 @@ -17,6 +17,9 @@ name = "branchless" [features] default = [] integration-test-bin = [] +test_gpg = [] +test_ssh = [] +test_x509 = [] [[bench]] harness = false @@ -56,6 +59,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/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/src/core/node_descriptors.rs b/git-branchless-lib/src/core/node_descriptors.rs index bc3b083ff..e9fc1d39a 100644 --- a/git-branchless-lib/src/core/node_descriptors.rs +++ b/git-branchless-lib/src/core/node_descriptors.rs @@ -516,9 +516,83 @@ 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}; + use std::str::FromStr; use std::time::Duration; use super::*; @@ -577,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-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/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-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 000000000..5b0e96575 Binary files /dev/null and b/git-branchless-lib/tests/test_data/gpg/pubring.kbx differ 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 000000000..0736c4700 Binary files /dev/null and b/git-branchless-lib/tests/test_data/gpg/pubring.kbx~ differ 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 000000000..dae832ff3 Binary files /dev/null and b/git-branchless-lib/tests/test_data/gpg/trustdb.gpg differ 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----- +MIIJKQIBAAKCAgEAuwV7YvEJrHX0RDAr4OnPcds07ITR+NQEBc8GxdmTgAYmoFDr +7VtYNz90KOtxMfGgjOywgq24v0TszNC7soVpi2Pha6eEV3kO/MyyL7POkgThiA1b +PbYmHes3VjykhDrPTnaqyw02JuKL05S3/ydnhY4WcqyPcyBzhNMgN2rXhMJY0C02 +mmEBVENV/6PkcKTiAswZm/5ffgC0bUQ0XvKLVryv73z/QW5HKzA7NpBpVu3BgYDQ +6Vg2QljHC93HbPQqsQIn4k0u49YZSkKsESaWq/W55w8/EG+1Rj+RqSrLul213jXX +T9UMEqrOOamJFR/EiXyCL8IyvhJoY8eX4V3p0oK216mlwQo3z5fsTaG+bjEpeKUG +3Rz7akZAs9+KRnRzsLDVPjoFoKYSRjbnrZEIqQJnKdhcD8J8tQHWVPD9YuoiMV+F +IuALaagcpVeI+IC5sjrIlLIedQtEOA/VVchb2r56rIl6g31c6EMgMDRCqHELpppE +p56o//vGkkJI725s+aBqKdj/YWq/YzjxH0AvrIb7JUgXyUKkwdaNCARRr24AgZs/ +o8us8/pZqXX7DuukhbBrS0y9LsX/GA5fWJzIkqOSfy9csH1l714gnrznikU9SYvf +9f0qygV+aGGVrLW51Q0pnvLJQOEy5DzHx5J0pI5GB2jWuXUajo1h4MwKwSsCAwEA +AQKCAgAwTpowVFJLWaxecJLk5X/PyO9CqIwT2a+wUGlPcYPH7c7MWhqWlKGUo689 +YpM6c08lF34AQx8VSJhhzwisdAlvF72CMSLd9UcJxVXBPJB+5wPaQJjwt7vgvoDs +pX1ZKlehzMUQ7bivEpVLcX6VSXhT5v+lXy0ub5NnG6GWruI8SHboTsVr2uKdAUeV +YKAA+JXoYw+6W1oPEd+I2bUtUTjsjpBjK8ZDAFkhrrOcxHBxI7h32u4bV82sFYJU +blS0r9zXG9dxfL4/221s4QvQ4kbI5A/Avw9rD2+jVYgqSgOVf98aJo4W8NDNgZED +E+d0rV/2o1J9Q4wXzGlqXIO+kqNya2juGkZTKUhSMGrRhMPd0d1K5bE2v0gYeyh9 +/ii+05ezLVliqK70Cy+Dsga8kN3oIBYJ7J3gSbKV6k2tudY++zlnW0WOF44Irpl2 +fWr2nvKHhGDeIHlQYa1ONkATj8ihmKGTMeJctfo5LFhSSKDu/3U1TopCjXR9iMUx +FJRcNpvguO27JKs61O9F0q1Y/BmCHToxAqi6LH0+d47hudx9YkzXmTdd2j5T1c3o +kLJwR9hOKn/urDp3vU2EtS/EFgd9r5fmIonEbcOjzQV+hftD82wHJi3v0Sf5y7WE +aLHeSCOJES+IIRn5Hn6CGwMobY3hoVSutPILneK06GJHRZqYAQKCAQEA5RtsLVk3 +bIUu2/zclW6tDXRdwHgkFwHKjT8a8oLnSP2raO6daIfE6L5JifW6IQT3upUJC7DJ +Nv4WaCu2U28gpn8G2yKUIxfLzZHOxMg1Sfl09eJW57jtx9/n3aUJKprKFmCosoQE +ietLQz/MKKW6Yz+pJt6eO2zfa1xxC0vdR5Ubj96mTgoEGd7r7nmSPnDY5uk/8GIU +JQNkDXiyELrAUgP41uovNbepz6DVbQ+pxv75tLEb2DdDWj3VucZYz5k0KLKfU3FQ +DK2Ut5fTUbjcvd67rgsaiUunouskB3o17JNPsy6YSZRJTn0Ka+/5DobtnhuRLyw7 +ssmkqTHBlKjs4QKCAQEA0PlmmQal7pUtLygISHrJCaYm8iqaSUJWmYkzrvvNj8c4 +BHHCLagCivIB+buHpdCAbwwMbyb3jKk2HjWg6+SB4QfI7yOBO1EDC973InVzNxiz +fGy8j3yAkF870S+qvjw0ctI215rP4ijLW48caLikAaKKs8Sg+88TihUM+93YZd+9 +z3rBsRe7Mjx+CVSIBk4Y97erUYNtLEcwdxfTKF16gAF3QPZBgbDXp4zQqxy9ygWE +Mq6snBE7ZOZ90CyzQKtxARTHQz2KL8BQuEycyaSbZrRKzv6gbTh/AJblC1+XyKOZ +XP7/omZqYP3ymCSzKZRxBZr5tYqLEXbcLG1YM3uDiwKCAQAjb/dumEMrZPpXiqgv +8PuRfjNWJv8mE9/Zsy4e3sKZlqshwu7UEVQS0AQd88VDgDo1QQIyLbkh6XFb0Y8V +HrZFKpbL5HBXcUHT3T7HT1R2ycAenFDm7MLSzL5XmURus5yYk4Dkg+kT0DJHB6Iz +k1beLSWj3oFJHdD9EUJnBegHP3gQ69Z7ca8qtUF3FJTbVacnKGL8cp89DmY2cznP +vqMqzHR8AaMlwu/gLrd0dBzFrADBwzdUXU/ssG3Tm2t3SPI5bU+Zn4hLPbooH7LP +DDIQqFgNVqhXHj2ixI5BHdUjb7G/PHDPyMECA/NNuMOkTJiKgScWzde6EtiGVyzj +KrHhAoIBAQDI40gOyUZQMVXLZEd6LZb/UJtV50CENmJ7nFpz15pHojKmixkovL8d +BQKDKRSAgQxGNCXr4GDO9oeQyOsWeYelZM9znAUKmZk9Gy0mEhQdHgDFFt65bO1G +XFZrhNA1qjidPJn979PxzFeZf9zGiuEWtUNzBw3eo7Vb41qk8SGq5UhZoN98N5z7 +8Q0UOQ++R+tWj1kAtkAH9bOQePXDcwFk9oPGjjRw0Siu5/5cz/desqjf8+z45tPa +7nx5187F10l9yZS4fftOZijy0qtSaIv/Ukgr7rkX5dypG2W55j5KYYL31Ca43o1b +CiTgvz8ANLPluJJeQ33m3wVPLBpC+3R7AoIBAQDV0OKf9osfBvGDqFnUmx6IBoG2 +c95eZN57+ym8ROgV6afuC6cm0SZhvVItocpTBhNgZ0U9yEct/gA34AIc0jZHqw4m +cw9nfg1CwqvJNUwgTj+68yq4ZrS3tImKCoaNAjV5SD+BZUKzw7npJsLPNjKstUWt +Z6DZITd+4cVHfv8Wutrl7Si0LxOrDsNvXDlrOuQJ9n3bWUC9W1siiuDDgAVWTEx6 +lUsaora+q2lMozuNp88zCHhpfYLTlPANazFhzwgfWo2RoWioas4W71+Y7F+DFgFo +g/0t3/kLuqzX05T+rHGBrW3SRkL9AvH1wi6Fjq7iowMoI3v4Y7taGPaT5R6b +-----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 000000000..2a58a8f49 Binary files /dev/null and b/git-branchless-lib/tests/test_data/x509/ca.p12 differ 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 000000000..8dfc614b7 Binary files /dev/null and b/git-branchless-lib/tests/test_data/x509/git.p12 differ diff --git a/git-branchless-lib/tests/test_data/x509/gpgsm.conf b/git-branchless-lib/tests/test_data/x509/gpgsm.conf new file mode 100644 index 000000000..7a85f072d --- /dev/null +++ b/git-branchless-lib/tests/test_data/x509/gpgsm.conf @@ -0,0 +1,7 @@ + +###+++--- GPGConf ---+++### +disable-crl-checks +###+++--- GPGConf ---+++### Sat Apr 19 09:36:56 2025 PDT +# GPGConf edited this configuration file. +# It will disable options before this marked block, but it will +# never change anything below these lines. diff --git a/git-branchless-lib/tests/test_data/x509/private-keys-v1.d/04CE1B8A4A58B3CA21F7E57C764B0E3CF0A0C62C.key b/git-branchless-lib/tests/test_data/x509/private-keys-v1.d/04CE1B8A4A58B3CA21F7E57C764B0E3CF0A0C62C.key new file mode 100644 index 000000000..e4edbc339 --- /dev/null +++ b/git-branchless-lib/tests/test_data/x509/private-keys-v1.d/04CE1B8A4A58B3CA21F7E57C764B0E3CF0A0C62C.key @@ -0,0 +1,54 @@ +Created: 20250419T163832 +Key: (private-key (rsa (n #00BB057B62F109AC75F444302BE0E9CF71DB34EC84D1 + F8D40405CF06C5D993800626A050EBED5B58373F7428EB7131F1A08CECB082ADB8BF44 + ECCCD0BBB285698B63E16BA78457790EFCCCB22FB3CE9204E1880D5B3DB6261DEB3756 + 3CA4843ACF4E76AACB0D3626E28BD394B7FF2767858E1672AC8F73207384D320376AD7 + 84C258D02D369A6101544355FFA3E470A4E202CC199BFE5F7E00B46D44345EF28B56BC + AFEF7CFF416E472B303B36906956EDC18180D0E958364258C70BDDC76CF42AB10227E2 + 4D2EE3D6194A42AC112696ABF5B9E70F3F106FB5463F91A92ACBBA5DB5DE35D74FD50C + 12AACE39A989151FC4897C822FC232BE126863C797E15DE9D282B6D7A9A5C10A37CF97 + EC4DA1BE6E312978A506DD1CFB6A4640B3DF8A467473B0B0D53E3A05A0A6124636E7AD + 9108A9026729D85C0FC27CB501D654F0FD62EA22315F8522E00B69A81CA55788F880B9 + B23AC894B21E750B44380FD555C85BDABE7AAC897A837D5CE84320303442A8710BA69A + 44A79EA8FFFBC6924248EF6E6CF9A06A29D8FF616ABF6338F11F402FAC86FB254817C9 + 42A4C1D68D080451AF6E00819B3FA3CBACF3FA59A975FB0EEBA485B06B4B4CBD2EC5FF + 180E5F589CC892A3927F2F5CB07D65EF5E209EBCE78A453D498BDFF5FD2ACA057E6861 + 95ACB5B9D50D299EF2C940E132E43CC7C79274A48E460768D6B9751A8E8D61E0CC0AC1 + 2B#)(e #010001#)(d #304E9A3054524B59AC5E7092E4E57FCFC8EF42A88C13D9AFB0 + 50694F7183C7EDCECC5A1A9694A194A3AF3D62933A734F25177E00431F15489861CF08 + AC74096F17BD823122DDF54709C555C13C907EE703DA4098F0B7BBE0BE80ECA57D592A + 57A1CCC510EDB8AF12954B717E95497853E6FFA55F2D2E6F93671BA196AEE23C4876E8 + 4EC56BDAE29D01479560A000F895E8630FBA5B5A0F11DF88D9B52D5138EC8E90632BC6 + 43005921AEB39CC4707123B877DAEE1B57CDAC1582546E54B4AFDCD71BD7717CBE3FDB + 6D6CE10BD0E246C8E40FC0BF0F6B0F6FA355882A4A03957FDF1A268E16F0D0CD819103 + 13E774AD5FF6A3527D438C17CC696A5C83BE92A3726B68EE1A4653294852306AD184C3 + DDD1DD4AE5B136BF48187B287DFE28BED397B32D5962A8AEF40B2F83B206BC90DDE820 + 1609EC9DE049B295EA4DADB9D63EFB39675B458E178E08AE99767D6AF69EF2878460DE + 20795061AD4E3640138FC8A198A19331E25CB5FA392C585248A0EEFF75354E8A428D74 + 7D88C53114945C369BE0B8EDBB24AB3AD4EF45D2AD58FC19821D3A3102A8BA2C7D3E77 + 8EE1B9DC7D624CD799375DDA3E53D5CDE890B27047D84E2A7FEEAC3A77BD4D84B52FC4 + 16077DAF97E62289C46DC3A3CD057E85FB43F36C07262DEFD127F9CBB58468B1DE4823 + 89112F882119F91E7E821B03286D8DE1A154AEB4F20B9DE2B4E86247459A9801#)(p + #00D0F9669906A5EE952D2F2808487AC909A626F22A9A494256998933AEFBCD8FC738 + 0471C22DA8028AF201F9BB87A5D0806F0C0C6F26F78CA9361E35A0EBE481E107C8EF23 + 813B51030BDEF72275733718B37C6CBC8F7C80905F3BD12FAABE3C3472D236D79ACFE2 + 28CB5B8F1C68B8A401A28AB3C4A0FBCF138A150CFBDDD865DFBDCF7AC1B117BB323C7E + 095488064E18F7B7AB51836D2C47307717D3285D7A80017740F64181B0D7A78CD0AB1C + BDCA058432AEAC9C113B64E67DD02CB340AB710114C7433D8A2FC050B84C9CC9A49B66 + B44ACEFEA06D387F0096E50B5F97C8A3995CFEFFA2666A60FDF29824B3299471059AF9 + B58A8B1176DC2C6D58337B838B#)(q #00E51B6C2D59376C852EDBFCDC956EAD0D745D + C078241701CA8D3F1AF282E748FDAB68EE9D6887C4E8BE4989F5BA2104F7BA95090BB0 + C936FE16682BB6536F20A67F06DB22942317CBCD91CEC4C83549F974F5E256E7B8EDC7 + DFE7DDA5092A9ACA1660A8B2840489EB4B433FCC28A5BA633FA926DE9E3B6CDF6B5C71 + 0B4BDD47951B8FDEA64E0A0419DEEBEE79923E70D8E6E93FF062142503640D78B210BA + C05203F8D6EA2F35B7A9CFA0D56D0FA9C6FEF9B4B11BD837435A3DD5B9C658CF993428 + B29F5371500CAD94B797D351B8DCBDDEBBAE0B1A894BA7A2EB24077A35EC934FB32E98 + 4994494E7D0A6BEFF90E86ED9E1B912F2C3BB2C9A4A931C194A8ECE1#)(u + #00D5D0E29FF68B1F06F183A859D49B1E880681B673DE5E64DE7BFB29BC44E815E9A7 + EE0BA726D12661BD522DA1CA5306136067453DC8472DFE0037E0021CD23647AB0E2673 + 0F677E0D42C2ABC9354C204E3FBAF32AB866B4B7B4898A0A868D023579483F816542B3 + C3B9E926C2CF3632ACB545AD67A0D921377EE1C5477EFF16BADAE5ED28B42F13AB0EC3 + 6F5C396B3AE409F67DDB5940BD5B5B228AE0C38005564C4C7A954B1AA2B6BEAB694CA3 + 3B8DA7CF330878697D82D394F00D6B3161CF081F5A8D91A168A86ACE16EF5F98EC5F83 + 16016883FD2DDFF90BBAACD7D394FEAC7181AD6DD24642FD02F1F5C22E858EAEE2A303 + 28237BF863BB5A18F693E51E9B#))) diff --git a/git-branchless-lib/tests/test_data/x509/private-keys-v1.d/888D249FE838FA4E490BA732F0ED514E8DD61EB6.key b/git-branchless-lib/tests/test_data/x509/private-keys-v1.d/888D249FE838FA4E490BA732F0ED514E8DD61EB6.key new file mode 100644 index 000000000..2f6b62890 --- /dev/null +++ b/git-branchless-lib/tests/test_data/x509/private-keys-v1.d/888D249FE838FA4E490BA732F0ED514E8DD61EB6.key @@ -0,0 +1,54 @@ +Created: 20250419T163839 +Key: (private-key (rsa (n #00B4C495D36EBD7568628D20953C373578DB8F5BE26F + 61BF863A9F8725C9C6100A2048E004DE7DC7EB3169B820C5BB97A32450400E3427D2D1 + 25E446928B406C504F7CB7CC92893092131329B6912B484C08D6E5869490187710BE4D + ACCF1681E7D8703E612266F7011779632DEA919D0EAA79654A0DE6257B666C34ACB455 + 6A663BF72BED39C390FB97A9C84D3FEF19FB2AD44E36E2B58C57F6D14400B2E615B756 + 66EF50BA64019BBC18708460316F7936739F0E3452B309087F17827C8A1F9CA72C64A9 + 29C9372AF48CB40474A62B0D9361FB1EA0A06593537E133D2F1455E97CADDB7F02A93C + BDC41C90F60FA178132A493477520370E0285AA32A5EAD1B7951C1A6B963EE2D479B6F + 424CD5DD418FA73B78EA4393EFFA136F6BA95124C4102C0C7401574D520A3A3D178B05 + A769152EB06736B1763654E5FBAD930B7974F7C3064E497A3A4DE27CC670DB65024029 + 046BA08BA925802449E9DF85DDD65C5B8B5E9073BBC82F5D604D67BCD1C1AB94606C7F + 3A82F3B2FCCA3B5176E14031BABC72160654780E8CCAA306C74E93FE49177CE4A19676 + 28D16A88C5D2326491D22E755D8B779BF613427931C35BCC7C91F68BA03741F7809B31 + 6434F88E5E47C86FBB5E4CF0BAF0326CFB32C4C766DED8CF8B73DF9E010A2104DB6237 + 36C8099893CB6FC9B931D44271820C07E7CC63B02DB53791789C5B2ABB469E6ACF4662 + 65#)(e #010001#)(d #78D6249566A46D346F1489E058954B4053FCBBDE5CB849D00F + A60E309B75B3C92115B5F26B4C477497DE64C03B8D8FEAF55E53C36AE7E80BF9396457 + 97906417375B567E00C2865E4F33F321605F3F7FDC34D2CFA8C5BE24F16355071C939E + 18FE5CE1C60ABA2B896648B59CBEFDC1B04912C73BBE98C71B7B36FFEB26CD2647DAF3 + FA4024E9EF23CF9926154594AE987063E538D949A4DC3458650DD688E9A159B78B683A + 809976A240F00DFD436E91E76D58B8A50F5730C5A6376FF99257595F82D4732444A685 + 275E5B03D37A34B3697F08DFCB5E0D28739A867FD2D5D42C3534FED22354400F566606 + 1D060BD8F96EBDEABD8C6C526AF56EC93D8C7649332A3E0916064488D97978636732A6 + 3766599411103C2389920725189D28C5A9157D694135440A63AA4E37F3ACB46DEA1A92 + 0B8C01A6D832D975B28D381B1DB60144A316027A830FAFFD070679DC92769C5C10BB82 + 385600487A94AFD9C572C0E55217E4195BC5850F8CEBD1CDF2F135C429EB4D60CF9A06 + 95F2478B4945047888404B2E7D02AC60E6C6D3415394C9A8FB6426B5DD43A632228862 + 2902C65F454BE6B96E9875D4903A1E4632D5E73CB3C07F591B2F6B297B89DCE224CB72 + 77038A8CCCA37C929DE8599EFC42FFEC4C0E6DEB5551F2B3F539CE1CCC3D795425E862 + 68D87A9F7A28AC68C24297F4ECE7E5FE496E8AD09E3F15DA1ECEAF60EA00CAC1#)(p + #00C450F6980D0E2915804FDF9401F6CA9EA28146171B32A243C7F4D734AE909F50FE + EF63634E70F03DF68697D234C43652FFAED01610FAECBC0AD6CB32F00F659660613BF9 + 1759BC374D2AC97DE431A5AB67DA373C6DB4483E6D97F2C490AD48CADC24853C6A793C + D0EAF10B09B5ABF1D71D2C81B1C0DFB1DAE6AFF3D86819AFCFAFBF564573CC9FDBD51D + F58E8332F6D9D0E671022297D5582CC54A8BD076D0AC607DF413B74A18100DA059A7A2 + 90FC97FE90F4DB4EEFB2E0DE3FD8ADC5AE7F73D2C8FEA05D30CD9FC12069534DB76035 + 22AAF82441427866FE512BD12F0DE124613B64D9E694796DF9EF5130162B9AF6CF4C32 + CDF717E737FFD7C642605C5371#)(q #00EBB983A498564A03AED4AD364CD2FFDD3B7A + 9C7377EC7E23DE6E80B0F4ACFDBC0012AD640DB08D212D830195CA0488589CFB2981D5 + CE0A2DD70639426106120714CB84AFBCFB48D89A8B7CD1AC3209A8CC1C4F247257526C + BF7FA8E5A9FFEB1623B5F60225DE3FF0B163AF8885FE0E16B7B28D2801B12465FA8930 + 47B05E1C8A421217F8EC117218DD62C0C94F818E3A3D2955C1B2DA76F9888899828428 + F39990ABE95640E82CB59E643053E72BBD6B91109ED6D7CCD510B81B58868262D5AC83 + FCE573014D4F4BDE71630BB24F43C58742B4B4D2EB3D1C3012E2BA30F0BA5195B775E7 + 38E7B46D322C520637A9CD1F8A6E40C3A9D0F4C19D003C91E9B4DC35#)(u + #00AEAFE674B90ACB57A5805C371BB747D41105250BC16EA5713EB8D0DC52EF893992 + 76762F91B1E7C2F8060F6C51C80D2C209F082688A587A9DF908C78A4F5302025AB0107 + 2CAAEB94E6446DABF5AAC94CB75A1E12785D7A30AC171DE04174C09FE8582475FA3B5B + 9B399C0EC0BFFBF565B1469A4EEE9ED4B1DEB1F812221879E4F7C3D06190490502A5A0 + 6C9BADD45697C1A7616ABD904A802B1ED6556B5B5DC04EBDD6F473FE4FA4B43F0751C5 + F3772B825A513C801CAB112BC99826F7B3A45D72A12FB6036D7F7E155B7B3D12803318 + 23B36A19BE929596439B29A14735E768A3ABAC6C42EF43663821C342B989982AEFF98F + 25F9F2D2E6A7352B0D6D481CA4#))) 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 000000000..401d80f3e Binary files /dev/null and b/git-branchless-lib/tests/test_data/x509/pubring.kbx differ 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 000000000..fffe9b786 Binary files /dev/null and b/git-branchless-lib/tests/test_data/x509/pubring.kbx~ differ diff --git a/git-branchless-lib/tests/test_data/x509/trustlist.txt b/git-branchless-lib/tests/test_data/x509/trustlist.txt new file mode 100644 index 000000000..e7f9a5dd9 --- /dev/null +++ b/git-branchless-lib/tests/test_data/x509/trustlist.txt @@ -0,0 +1,2 @@ +04:C5:AC:2C:55:EF:83:E6:4F:52:2C:B3:D2:A7:DF:0B:90:B9:C0:F2 S relax +64:A4:3D:AE:5E:EF:18:25:58:47:1F:09:84:60:F8:48:33:55:3E:43 S relax 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-lib/tests/test_sign.rs b/git-branchless-lib/tests/test_sign.rs new file mode 100644 index 000000000..9a511e1d5 --- /dev/null +++ b/git-branchless-lib/tests/test_sign.rs @@ -0,0 +1,173 @@ +use std::collections::HashMap; + +use branchless::core::config::env_vars::{ + get_path_to_gpg, get_path_to_gpgsm, get_path_to_ssh_keygen, +}; +use branchless::testing::{make_git, GitRunOptions}; + +/// Requires `gpg` to be installed and its path provided in the `TEST_GPG` environment variable. +#[cfg(feature = "test_gpg")] +mod gpg_tests { + use super::*; + + #[test] + fn test_valid_gpg_configuration() -> 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(()) + } +} diff --git a/git-branchless-move/src/lib.rs b/git-branchless-move/src/lib.rs index 86b081dea..02ed299f1 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; @@ -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-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..69cbf038f 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)] @@ -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. @@ -358,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". @@ -639,6 +691,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 166f6539b..90f9ce98e 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; @@ -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-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..02a1a31a5 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; @@ -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-smartlog/src/lib.rs b/git-branchless-smartlog/src/lib.rs index e3b3bdc3f..d9bb96901 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; @@ -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, }, ) } 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(()) +} 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-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 add7cd898..9d1d3a163 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; @@ -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-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/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)?; 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/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(()) 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;