From 5c305e922e19e4ac65c6a1473be67145a1c73f2b Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Wed, 25 Feb 2026 16:46:02 +0000 Subject: feat: forward unrecognised push options to git servers Any -o option passed to `git push` that is not handled by ngit (title, description) is forwarded verbatim to the git server via git2::PushOptions::remote_push_options. This allows options such as `-o secret-scanning.skip` to pass through transparently. `ngit send` gains a matching -o / --push-option flag for the same purpose. --- src/bin/git_remote_nostr/main.rs | 14 ++++++++++++-- src/bin/git_remote_nostr/push.rs | 16 ++++++++++++++++ src/bin/ngit/sub_commands/send.rs | 38 +++++++++++++++++++++++--------------- src/bin/ngit/sub_commands/sync.rs | 1 + 4 files changed, 52 insertions(+), 17 deletions(-) (limited to 'src/bin') diff --git a/src/bin/git_remote_nostr/main.rs b/src/bin/git_remote_nostr/main.rs index e0821e9..6186ed3 100644 --- a/src/bin/git_remote_nostr/main.rs +++ b/src/bin/git_remote_nostr/main.rs @@ -28,6 +28,7 @@ use crate::{client::Client, git::Repo}; struct PushOptions { title: Option, description: Option, + git_server_extras: Vec, } /// Strip git's c-style quoting from a push-option value. @@ -118,6 +119,7 @@ mod list; mod push; #[tokio::main] +#[allow(clippy::too_many_lines)] async fn main() -> Result<()> { if std::env::var("NGITTEST").is_ok() { std::env::set_var("NGIT_VERBOSE", "1"); @@ -177,16 +179,23 @@ async fn main() -> Result<()> { } ["option", "push-option", rest @ ..] => { let option = strip_git_quoting(&rest.join(" ")); - if let Some((key, value)) = option.split_once('=') { + let handled_by_ngit = if let Some((key, value)) = option.split_once('=') { match key { "title" => { push_options.title = Some(decode_push_option_escapes(value)); + true } "description" => { push_options.description = Some(decode_push_option_escapes(value)); + true } - _ => {} + _ => false, } + } else { + false + }; + if !handled_by_ngit { + push_options.git_server_extras.push(option); } println!("ok"); } @@ -206,6 +215,7 @@ async fn main() -> Result<()> { &mut client, list_outputs.clone(), title_description, + push_options.git_server_extras.clone(), ) .await?; push_options = PushOptions::default(); diff --git a/src/bin/git_remote_nostr/push.rs b/src/bin/git_remote_nostr/push.rs index b64cdd9..06624f4 100644 --- a/src/bin/git_remote_nostr/push.rs +++ b/src/bin/git_remote_nostr/push.rs @@ -45,6 +45,7 @@ use repo_state::RepoState; use crate::{client::Client, git::Repo}; #[allow(clippy::too_many_lines)] +#[allow(clippy::too_many_arguments)] #[allow(clippy::type_complexity)] pub async fn run_push( git_repo: &Repo, @@ -54,6 +55,7 @@ pub async fn run_push( client: &mut Client, list_outputs: Option, bool)>>, title_description: Option<(String, String)>, + git_server_push_options: Vec, ) -> Result<()> { let refspecs = get_refspecs_from_push_batch(stdin, initial_refspec)?; @@ -132,6 +134,7 @@ pub async fn run_push( existing_state, &term, title_description.as_ref(), + &git_server_push_options, ) .await?; @@ -159,6 +162,8 @@ pub async fn run_push( .cloned() .collect::>(); if !refspecs.is_empty() { + let push_options_refs: Vec<&str> = + git_server_push_options.iter().map(String::as_str).collect(); let _ = push_to_remote( git_repo, &git_server_url, @@ -166,6 +171,7 @@ pub async fn run_push( &remote_refspecs, &term, is_grasp_server_clone_url(&git_server_url), + &push_options_refs, ); } } @@ -187,6 +193,7 @@ async fn create_and_publish_events_and_proposals( existing_state: HashMap, term: &Term, title_description: Option<&(String, String)>, + git_server_push_options: &[String], ) -> Result<(Vec, bool)> { let (signer, mut user_ref, _) = load_existing_login( &Some(git_repo), @@ -281,6 +288,7 @@ async fn create_and_publish_events_and_proposals( &signer, term, title_description, + git_server_push_options, ) .await?; for e in proposal_events { @@ -315,6 +323,7 @@ async fn process_proposal_refspecs( signer: &Arc, term: &Term, title_description: Option<&(String, String)>, + git_server_push_options: &[String], ) -> Result<(Vec, Vec)> { let mut events = vec![]; let mut rejected_proposal_refspecs = vec![]; @@ -357,6 +366,7 @@ async fn process_proposal_refspecs( signer, term, title_description, + git_server_push_options, ) .await? { @@ -398,6 +408,7 @@ async fn process_proposal_refspecs( signer, term, title_description, + git_server_push_options, ) .await? { @@ -469,6 +480,7 @@ async fn process_proposal_refspecs( signer, term, title_description, + git_server_push_options, ) .await? { @@ -492,6 +504,7 @@ async fn generate_patches_or_pr_event_or_pr_updates( signer: &Arc, term: &Term, title_description: Option<&(String, String)>, + git_server_push_options: &[String], ) -> Result> { let parent_is_pr = root_proposal.is_some_and(|proposal| proposal.kind.eq(&KIND_PULL_REQUEST)); let use_pr = parent_is_pr || git_repo.are_commits_too_big_for_patches(ahead); @@ -499,6 +512,8 @@ async fn generate_patches_or_pr_event_or_pr_updates( if use_pr { let tip = ahead.first().context("no commits")?; // ahead is youngest first let first_commit = ahead.last().context("no commits")?; + let push_options_refs: Vec<&str> = + git_server_push_options.iter().map(String::as_str).collect(); select_servers_push_refs_and_generate_pr_or_pr_update_event( client, git_repo, @@ -512,6 +527,7 @@ async fn generate_patches_or_pr_event_or_pr_updates( signer, false, term, + &push_options_refs, ) .await .context(format!( diff --git a/src/bin/ngit/sub_commands/send.rs b/src/bin/ngit/sub_commands/send.rs index 325ad89..6b18e84 100644 --- a/src/bin/ngit/sub_commands/send.rs +++ b/src/bin/ngit/sub_commands/send.rs @@ -50,6 +50,9 @@ pub struct SubCommandArgs { /// publish as Patches even if they may be > 60kb #[arg(long, action)] pub(crate) force_patch: bool, + #[clap(long = "push-option", short = 'o', value_parser, num_args = 0..)] + /// git push options to pass to the git server (eg. -o secret-scanning.skip) + pub(crate) push_options: Vec, } /// Validates send command arguments for non-interactive mode. @@ -351,21 +354,26 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs, no_fetch: bool) -> Re let events = if as_pr { let tip = commits.last().context("no commits")?; // commits has been reversed to oldest first let first_commit = commits.first().context("no commits")?; - select_servers_push_refs_and_generate_pr_or_pr_update_event( - &client, - &git_repo, - &repo_ref, - tip, - first_commit, - git_repo.get_commit_parent(first_commit).ok().as_ref(), - &mut user_ref, - root_proposal.as_ref(), - &cover_letter_title_description, - &signer, - true, - &console::Term::stdout(), - ) - .await? + { + let push_options_refs: Vec<&str> = + args.push_options.iter().map(String::as_str).collect(); + select_servers_push_refs_and_generate_pr_or_pr_update_event( + &client, + &git_repo, + &repo_ref, + tip, + first_commit, + git_repo.get_commit_parent(first_commit).ok().as_ref(), + &mut user_ref, + root_proposal.as_ref(), + &cover_letter_title_description, + &signer, + true, + &console::Term::stdout(), + &push_options_refs, + ) + .await? + } } else { let events = generate_cover_letter_and_patch_events( cover_letter_title_description.clone(), diff --git a/src/bin/ngit/sub_commands/sync.rs b/src/bin/ngit/sub_commands/sync.rs index daebb1b..b377ab4 100644 --- a/src/bin/ngit/sub_commands/sync.rs +++ b/src/bin/ngit/sub_commands/sync.rs @@ -187,6 +187,7 @@ pub async fn launch(args: &SubCommandArgs) -> Result<()> { &refspecs, &term, *is_grasp_server || is_grasp_server_clone_url(url), + &[], ) { Err(error) => { term.write_line(&format!( -- cgit v1.2.3