diff --git a/Cargo.lock b/Cargo.lock index 087cd5b16..04b1b4659 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1523,6 +1523,7 @@ dependencies = [ "git-branchless-testing", "git-branchless-undo", "git-record", + "git2-ext", "insta", "itertools 0.10.5", "lazy_static", @@ -1611,6 +1612,7 @@ dependencies = [ "git-branchless-testing", "git-record", "git2", + "git2-ext", "indicatif", "insta", "itertools 0.10.5", @@ -1908,6 +1910,21 @@ dependencies = [ "url", ] +[[package]] +name = "git2-ext" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a02e722c7e38969577e2f7a59b13fde0ee93e32b21d64bcbc3befedb9dd57a8b" +dependencies = [ + "bstr", + "git2", + "itertools 0.10.5", + "log", + "shlex", + "tempfile", + "which", +] + [[package]] name = "glob" version = "0.3.1" @@ -3968,6 +3985,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/git-branchless-lib/Cargo.toml b/git-branchless-lib/Cargo.toml index 3bb3c7621..b21d9cd98 100644 --- a/git-branchless-lib/Cargo.toml +++ b/git-branchless-lib/Cargo.toml @@ -57,6 +57,7 @@ eyre = "0.6.8" futures = "0.3.28" git-record = { version = "0.3", path = "../git-record" } git2 = { version = "0.17.2", default-features = false } +git2-ext = "0.6.0" indicatif = { version = "0.17.5", features = ["improved_unicode"] } itertools = "0.10.3" lazy_static = "1.4.0" diff --git a/git-branchless-lib/src/core/rewrite/execute.rs b/git-branchless-lib/src/core/rewrite/execute.rs index 98186fcda..46c04ca74 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}; @@ -435,7 +435,8 @@ mod in_memory { use crate::core::rewrite::move_branches; use crate::core::rewrite::plan::{OidOrLabel, RebaseCommand, RebasePlan}; use crate::git::{ - CherryPickFastError, CherryPickFastOptions, GitRunInfo, MaybeZeroOid, NonZeroOid, Repo, + self, CherryPickFastError, CherryPickFastOptions, GitRunInfo, MaybeZeroOid, NonZeroOid, + Repo, }; use crate::util::EyreExitOr; @@ -498,6 +499,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; @@ -535,6 +537,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 } => { @@ -628,12 +632,12 @@ mod in_memory { }; let rebased_commit_oid = repo .create_commit( - None, &commit_to_apply.get_author(), &committer_signature, commit_message, &commit_tree, vec![¤t_commit], + signer.as_deref(), ) .wrap_err("Applying rebased commit")?; @@ -748,12 +752,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")?; @@ -864,6 +868,7 @@ mod in_memory { force_on_disk: _, resolve_merge_conflicts: _, check_out_commit_options, + sign_option: _, } = options; // Note that if an OID has been mapped to multiple other OIDs, then the last @@ -959,6 +964,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); @@ -1073,6 +1079,16 @@ mod on_disk { ) })?; + let gpg_sign_opt_file = rebase_state_dir.join("gpg_sign_opt"); + if let Some(sign_flag) = sign_option.as_rebase_flag(repo)? { + std::fs::write(&gpg_sign_opt_file, sign_flag).wrap_err_with(|| { + format!( + "Writing `gpg_sign_opt` to: {:?}", + gpg_sign_opt_file.as_path() + ) + })?; + } + let end_file_path = rebase_state_dir.join("end"); std::fs::write( end_file_path.as_path(), @@ -1132,6 +1148,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)? { @@ -1176,6 +1193,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. @@ -1221,6 +1241,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 62f4a64c7..69529148f 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::{ 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/repo.rs b/git-branchless-lib/src/git/repo.rs index 5a8088b32..99a2830ed 100644 --- a/git-branchless-lib/src/git/repo.rs +++ b/git-branchless-lib/src/git/repo.rs @@ -24,6 +24,7 @@ use chrono::NaiveDateTime; 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}; @@ -1152,31 +1153,81 @@ 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)) + } + + /// Amend a commit with all non-`None` values + #[instrument(skip(signer))] + pub fn amend_commit( + &self, + commit_to_amend: &Commit, + author: Option<&Signature>, + committer: Option<&Signature>, + message: Option<&str>, + tree: Option<&Tree>, + signer: Option<&dyn Sign>, + ) -> Result { + macro_rules! owning_unwrap_or { + ($name:ident, $value_source:expr) => { + let owned_value; + let $name = if let Some(value) = $name { + value + } else { + owned_value = $value_source; + &owned_value + }; + }; + } + owning_unwrap_or!(author, commit_to_amend.get_author()); + owning_unwrap_or!(committer, commit_to_amend.get_committer()); + owning_unwrap_or!( + message, + commit_to_amend + .inner + .message_raw() + .ok_or(Error::DecodeUtf8 { + item: "raw message", + })? + ); + owning_unwrap_or!(tree, commit_to_amend.get_tree()?); + + let parents = commit_to_amend.get_parents(); + let parents = parents.iter().map(|parent| &parent.inner).collect_vec(); + + let oid = git2_ext::ops::commit( + &self.inner, + &author.inner, + &committer.inner, + message, + &tree.inner, + parents.as_slice(), + signer, + ) + .map_err(Error::Amend)?; Ok(make_non_zero_oid(oid)) } @@ -1365,12 +1416,12 @@ impl Repo { vec![] }; 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..de550b94b --- /dev/null +++ b/git-branchless-lib/src/git/sign.rs @@ -0,0 +1,108 @@ +use tracing::instrument; + +use super::{repo::Result, Repo, RepoError}; + +/// 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) -> Result> { + Ok(match self { + Self::UseConfig => { + let config = repo.inner.config().map_err(RepoError::ReadConfig)?; + match config.get_bool("commit.gpgsign").ok() { + 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. +#[instrument] +pub fn get_signer( + repo: &Repo, + option: &SignOption, +) -> Result>> { + match option { + SignOption::UseConfig | SignOption::UseConfigKey => { + let config = repo.inner.config().map_err(RepoError::ReadConfig)?; + if *option == SignOption::UseConfig { + if config.get_bool("commit.gpgsign").ok() == Some(false) { + return Ok(None); + } + } + 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.inner.config().map_err(RepoError::ReadConfig)?; + let format = config + .get_string("gpg.format") + .unwrap_or_else(|_| "openpgp".to_owned()); + let signer = match format.as_str() { + "openpgp" => { + let program = config + .get_string("gpg.openpgp.program") + .or_else(|_| config.get_string("gpg.program")) + .unwrap_or_else(|_| "gpg".to_owned()); + + Box::new(git2_ext::ops::GpgSign::new(program, keyid.to_string())) + as Box + } + "x509" => { + let program = config + .get_string("gpg.x509.program") + .unwrap_or_else(|_| "gpgsm".to_owned()); + + Box::new(git2_ext::ops::GpgSign::new(program, keyid.to_string())) + as Box + } + "ssh" => { + let program = config + .get_string("gpg.ssh.program") + .unwrap_or_else(|_| "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), + ))) + } + }; + 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 148bce4f9..068e7f8aa 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)?, @@ -365,12 +365,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) } @@ -452,7 +452,6 @@ branchless: automated working copy snapshot } ); let commit_oid = repo.create_commit( - None, &signature, &signature, &message, @@ -461,6 +460,7 @@ branchless: automated working copy snapshot Some(parent_commit) => vec![parent_commit], None => vec![], }, + None, )?; Ok(commit_oid) } diff --git a/git-branchless-lib/tests/test_rewrite_plan.rs b/git-branchless-lib/tests/test_rewrite_plan.rs index 0cad96ab8..e47657444 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 git_branchless_testing::{make_git, Git}; #[test] @@ -713,6 +714,7 @@ fn create_and_execute_plan( reset: false, render_smartlog: false, }, + sign_option: SignOption::Disable, }; let git_run_info = git.get_git_run_info(); let result = execute_rebase_plan( diff --git a/git-branchless-move/src/lib.rs b/git-branchless-move/src/lib.rs index c988be41e..173e6e026 100644 --- a/git-branchless-move/src/lib.rs +++ b/git-branchless-move/src/lib.rs @@ -261,6 +261,7 @@ pub fn r#move( resolve_merge_conflicts, dump_rebase_constraints, dump_rebase_plan, + ref sign_options, } = *move_options; let now = SystemTime::now(); let event_tx_id = event_log_db.make_transaction_id(now, "move")?; @@ -471,6 +472,7 @@ pub fn r#move( force_on_disk, resolve_merge_conflicts, check_out_commit_options: Default::default(), + sign_option: sign_options.to_owned().into(), }; execute_rebase_plan( effects, diff --git a/git-branchless-opts/src/lib.rs b/git-branchless-opts/src/lib.rs index df10ca5ac..cd658e617 100644 --- a/git-branchless-opts/src/lib.rs +++ b/git-branchless-opts/src/lib.rs @@ -91,6 +91,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. @@ -178,6 +182,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 { @@ -320,6 +364,10 @@ pub struct RecordArgs { /// if any. #[clap(action, short = 'I', long = "insert")] pub insert: bool, + + /// Options for signing commits. + #[clap(flatten)] + pub sign_options: SignOptions, } /// Display a nice graph of the commits you've recently worked on. @@ -602,6 +650,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 0f152eacc..64ff52017 100644 --- a/git-branchless-record/src/lib.rs +++ b/git-branchless-record/src/lib.rs @@ -31,7 +31,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; @@ -53,6 +53,7 @@ pub fn command_main(ctx: CommandContext, args: RecordArgs) -> EyreExitOr<()> { create, detach, insert, + sign_options, } = args; record( &effects, @@ -62,6 +63,7 @@ pub fn command_main(ctx: CommandContext, args: RecordArgs) -> EyreExitOr<()> { create, detach, insert, + &sign_options.into(), ) } @@ -74,6 +76,7 @@ fn record( branch_name: Option, detach: bool, insert: bool, + sign_option: &SignOption, ) -> EyreExitOr<()> { let now = SystemTime::now(); let repo = Repo::from_dir(&git_run_info.working_directory)?; @@ -147,19 +150,22 @@ fn record( &snapshot, event_tx_id, message.as_deref(), + sign_option, )?); } } else { - let args = { - let mut args = vec!["commit"]; - if let Some(message) = &message { - args.extend(["--message", message]); - } - if working_copy_changes_type == WorkingCopyChangesType::Unstaged { - args.push("--all"); - } - args - }; + let mut args = vec!["commit"]; + if let Some(message) = &message { + args.extend(["--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)?); } @@ -209,7 +215,8 @@ fn record( effects, git_run_info, now, - event_tx_id + event_tx_id, + sign_option, )?); } @@ -224,6 +231,7 @@ fn record_interactive( snapshot: &WorkingCopySnapshot, event_tx_id: EventTransactionId, message: Option<&str>, + sign_option: &SignOption, ) -> EyreExitOr<()> { let old_tree = snapshot.commit_stage0.get_tree()?; let new_tree = snapshot.commit_unstaged.get_tree()?; @@ -332,13 +340,15 @@ fn record_interactive( &update_index_script, )?; - let args = { - let mut args = vec!["commit"]; - if let Some(message) = message { - args.extend(["--message", message]); - } - args - }; + let mut args = vec!["commit"]; + if let Some(message) = message { + 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) } @@ -348,6 +358,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)?; @@ -475,6 +486,7 @@ To proceed anyways, run: git move -f -s 'siblings(.)", force_on_disk: false, resolve_merge_conflicts: false, check_out_commit_options: Default::default(), + sign_option: sign_option.to_owned(), }; let result = execute_rebase_plan( effects, diff --git a/git-branchless-reword/src/lib.rs b/git-branchless-reword/src/lib.rs index a648b7ee2..b93521376 100644 --- a/git-branchless-reword/src/lib.rs +++ b/git-branchless-reword/src/lib.rs @@ -42,7 +42,7 @@ use lib::core::rewrite::{ }; use lib::git::{message_prettify, Commit, GitRunInfo, MaybeZeroOid, NonZeroOid, Repo}; -use git_branchless_opts::{ResolveRevsetOptions, Revset}; +use git_branchless_opts::{ResolveRevsetOptions, Revset, SignOptions}; use git_branchless_revset::resolve_commits; /// The commit message(s) provided by the user. @@ -67,6 +67,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()?; @@ -290,6 +291,7 @@ pub fn reword( reset: false, render_smartlog: false, }, + sign_option: sign_options.into(), }; let result = execute_rebase_plan( effects, diff --git a/git-branchless-submit/src/phabricator.rs b/git-branchless-submit/src/phabricator.rs index 2721093e4..5d4e03625 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) @@ -608,6 +611,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 a1e3434ca..f8f8bcf4e 100644 --- a/git-branchless-test/src/lib.rs +++ b/git-branchless-test/src/lib.rs @@ -51,8 +51,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}; @@ -386,6 +387,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; @@ -414,6 +416,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)? { @@ -730,6 +733,7 @@ fn set_abort_trap( render_smartlog: false, ..Default::default() }, + sign_option: SignOption::Disable, }, )? { ExecuteRebasePlanResult::Succeeded { rewritten_oids: _ } => { @@ -1969,12 +1973,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/Cargo.toml b/git-branchless/Cargo.toml index 85fdcf9ee..ad36ad0e2 100644 --- a/git-branchless/Cargo.toml +++ b/git-branchless/Cargo.toml @@ -40,6 +40,7 @@ git-branchless-submit = { version = "0.7.0", path = "../git-branchless-submit" } git-branchless-test = { version = "0.7.0", path = "../git-branchless-test" } git-branchless-undo = { version = "0.7.0", path = "../git-branchless-undo" } git-record = { version = "0.3", path = "../git-record" } +git2-ext = "0.6.0" itertools = "0.10.5" lazy_static = "1.4.0" lib = { package = "git-branchless-lib", version = "0.7.0", path = "../git-branchless-lib" } diff --git a/git-branchless/src/commands/amend.rs b/git-branchless/src/commands/amend.rs index 7f97b55cb..6275f6a42 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 amended_commit_oid = head_commit.amend_commit( - None, + let sign_option = move_options.sign_options.to_owned().into(); + let signer = get_signer(&repo, &sign_option)?; + + let amended_commit_oid = repo.amend_commit( + &head_commit, 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: true, 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 6629eca83..c41af3988 100644 --- a/git-branchless/src/commands/mod.rs +++ b/git-branchless/src/commands/mod.rs @@ -138,6 +138,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 @@ -153,6 +154,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 280c7719e..c543293c8 100644 --- a/git-branchless/src/commands/sync.rs +++ b/git-branchless/src/commands/sync.rs @@ -69,6 +69,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, @@ -90,6 +91,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)?;