diff --git a/build.rs b/build.rs index 31619ef07f3..b5cc8149ffe 100644 --- a/build.rs +++ b/build.rs @@ -8,7 +8,7 @@ fn main() { .and_then(|out| parse_describe(&out.stdout)) .unwrap_or_else(|| env!("CARGO_PKG_VERSION").into()); - println!("cargo:rustc-env=GITOXIDE_VERSION={version}"); + println!("cargo:rustc-env=GIX_VERSION={version}"); } fn parse_describe(input: &[u8]) -> Option { diff --git a/examples/log.rs b/examples/log.rs index 8c8b157516f..be892f37e15 100644 --- a/examples/log.rs +++ b/examples/log.rs @@ -20,7 +20,7 @@ fn main() { } #[derive(Debug, clap::Parser)] -#[clap(name = "log", about = "git log example", version = option_env!("GITOXIDE_VERSION"))] +#[clap(name = "log", about = "git log example", version = option_env!("GIX_VERSION"))] struct Args { /// Alternative git directory to use #[clap(name = "dir", long = "git-dir")] diff --git a/examples/ls-tree.rs b/examples/ls-tree.rs index ecf607bf124..17af144040f 100644 --- a/examples/ls-tree.rs +++ b/examples/ls-tree.rs @@ -12,7 +12,7 @@ fn main() { } #[derive(Debug, clap::Parser)] -#[clap(name = "ls-tree", about = "git ls-tree example", version = option_env!("GITOXIDE_VERSION"))] +#[clap(name = "ls-tree", about = "git ls-tree example", version = option_env!("GIX_VERSION"))] #[clap(arg_required_else_help = true)] struct Args { /// Recurse into subtrees diff --git a/gix-command/src/lib.rs b/gix-command/src/lib.rs index b5baec40842..e23275e738e 100644 --- a/gix-command/src/lib.rs +++ b/gix-command/src/lib.rs @@ -68,6 +68,10 @@ pub struct Context { /// If `true`, set `GIT_ICASE_PATHSPECS` to `1`, to let patterns match case-insensitively, or `0` otherwise. /// If `None`, the variable won't be set. pub icase_pathspecs: Option, + /// If `true`, inherit `stderr` just like it's the default when spawning processes. + /// If `false`, suppress all stderr output. + /// If not `None`, this will override any value set with [`Prepare::stderr()`]. + pub stderr: Option, } mod prepare { @@ -237,6 +241,9 @@ mod prepare { if let Some(value) = ctx.icase_pathspecs { cmd.env("GIT_ICASE_PATHSPECS", usize::from(value).to_string()); } + if let Some(stderr) = ctx.stderr { + cmd.stderr(if stderr { Stdio::inherit() } else { Stdio::null() }); + } } cmd } diff --git a/gix-discover/tests/upwards/mod.rs b/gix-discover/tests/upwards/mod.rs index d15f9aa06c9..c1e575deae4 100644 --- a/gix-discover/tests/upwards/mod.rs +++ b/gix-discover/tests/upwards/mod.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use gix_discover::repository::Kind; fn expected_trust() -> gix_sec::Trust { - if std::env::var_os("GITOXIDE_TEST_EXPECT_REDUCED_TRUST").is_some() { + if std::env::var_os("GIX_TEST_EXPECT_REDUCED_TRUST").is_some() { gix_sec::Trust::Reduced } else { gix_sec::Trust::Full diff --git a/gix/src/config/cache/init.rs b/gix/src/config/cache/init.rs index 47f7c94808c..faf3cc8de1a 100644 --- a/gix/src/config/cache/init.rs +++ b/gix/src/config/cache/init.rs @@ -402,10 +402,16 @@ fn apply_environment_overrides( "gitoxide", Some(Cow::Borrowed("credentials".into())), git_prefix, - &[{ - let key = &gitoxide::Credentials::TERMINAL_PROMPT; - (env(key), key.name) - }], + &[ + { + let key = &gitoxide::Credentials::TERMINAL_PROMPT; + (env(key), key.name) + }, + { + let key = &gitoxide::Credentials::HELPER_STDERR; + (env(key), key.name) + }, + ], ), ( "gitoxide", @@ -435,6 +441,10 @@ fn apply_environment_overrides( let key = &gitoxide::Core::REFS_NAMESPACE; (env(key), key.name) }, + { + let key = &gitoxide::Core::EXTERNAL_COMMAND_STDERR; + (env(key), key.name) + }, ], ), ( diff --git a/gix/src/config/snapshot/credential_helpers.rs b/gix/src/config/snapshot/credential_helpers.rs index 5db6f339b5c..54499a1c34a 100644 --- a/gix/src/config/snapshot/credential_helpers.rs +++ b/gix/src/config/snapshot/credential_helpers.rs @@ -2,6 +2,7 @@ use std::{borrow::Cow, convert::TryFrom}; pub use error::Error; +use crate::config::cache::util::ApplyLeniency; use crate::{ bstr::{ByteSlice, ByteVec}, config::{ @@ -25,6 +26,8 @@ mod error { }, #[error("core.askpass could not be read")] CoreAskpass(#[from] gix_config::path::interpolate::Error), + #[error(transparent)] + BooleanConfig(#[from] crate::config::boolean::Error), } } @@ -145,7 +148,10 @@ impl Snapshot<'_> { .ignore_empty()? .map(|c| Cow::Owned(c.into_owned())), mode: self - .boolean(Credentials::TERMINAL_PROMPT.logical_name().as_str()) + .try_boolean(Credentials::TERMINAL_PROMPT.logical_name().as_str()) + .map(|val| Credentials::TERMINAL_PROMPT.enrich_error(val)) + .transpose() + .with_leniency(self.repo.config.lenient_config)? .and_then(|val| (!val).then_some(gix_prompt::Mode::Disable)) .unwrap_or_default(), } @@ -156,7 +162,12 @@ impl Snapshot<'_> { use_http_path, // The default ssh implementation uses binaries that do their own auth, so our passwords aren't used. query_user_only: url.scheme == gix_url::Scheme::Ssh, - ..Default::default() + stderr: self + .try_boolean(Credentials::HELPER_STDERR.logical_name().as_str()) + .map(|val| Credentials::HELPER_STDERR.enrich_error(val)) + .transpose() + .with_leniency(self.repo.options.lenient_config)? + .unwrap_or(true), }, gix_credentials::helper::Action::get_for_url(url.to_bstring()), prompt_options, diff --git a/gix/src/config/tree/sections/core.rs b/gix/src/config/tree/sections/core.rs index 50df2a77d9c..15ad9f947cb 100644 --- a/gix/src/config/tree/sections/core.rs +++ b/gix/src/config/tree/sections/core.rs @@ -17,7 +17,7 @@ impl Core { /// The `core.deltaBaseCacheLimit` key. pub const DELTA_BASE_CACHE_LIMIT: keys::UnsignedInteger = keys::UnsignedInteger::new_unsigned_integer("deltaBaseCacheLimit", &config::Tree::CORE) - .with_environment_override("GITOXIDE_PACK_CACHE_MEMORY") + .with_environment_override("GIX_PACK_CACHE_MEMORY") .with_note("if unset, we default to a small 64 slot fixed-size cache that holds at most 64 full delta base objects of any size. Set to 0 to deactivate it entirely"); /// The `core.disambiguate` key. pub const DISAMBIGUATE: Disambiguate = diff --git a/gix/src/config/tree/sections/gitoxide.rs b/gix/src/config/tree/sections/gitoxide.rs index ca443cff660..a3b05441263 100644 --- a/gix/src/config/tree/sections/gitoxide.rs +++ b/gix/src/config/tree/sections/gitoxide.rs @@ -116,6 +116,15 @@ mod subsections { pub const FILTER_PROCESS_DELAY: keys::Boolean = keys::Boolean::new_boolean("filterProcessDelay", &Gitoxide::CORE); + /// The `gitoxide.core.externalCommandStderr` key (default `true`). + /// + /// If `true`, the default, `stderr` of worktree filter programs, or any other git-context bearing command + /// invoked will be inherited. + /// If `false`, it will be suppressed completely. + pub const EXTERNAL_COMMAND_STDERR: keys::Boolean = + keys::Boolean::new_boolean("externalCommandStderr", &Gitoxide::CORE) + .with_environment_override("GIX_EXTERNAL_COMMAND_STDERR"); + /// The `gitoxide.core.refsNamespace` key. pub const REFS_NAMESPACE: RefsNamespace = keys::Any::new_with_validate("refsNamespace", &Gitoxide::CORE, super::validate::RefsNamespace) @@ -134,6 +143,7 @@ mod subsections { &Self::USE_STDEV, &Self::SHALLOW_FILE, &Self::FILTER_PROCESS_DELAY, + &Self::EXTERNAL_COMMAND_STDERR, &Self::REFS_NAMESPACE, ] } @@ -410,7 +420,7 @@ mod subsections { pub const CACHE_LIMIT: keys::UnsignedInteger = keys::UnsignedInteger::new_unsigned_integer("cacheLimit", &Gitoxide::OBJECTS) .with_note("If unset or 0, there is no object cache") - .with_environment_override("GITOXIDE_OBJECT_CACHE_MEMORY"); + .with_environment_override("GIX_OBJECT_CACHE_MEMORY"); /// The `gitoxide.objects.noReplace` key. pub const NO_REPLACE: keys::Boolean = keys::Boolean::new_boolean("noReplace", &Gitoxide::OBJECTS); /// The `gitoxide.objects.replaceRefBase` key. @@ -467,6 +477,13 @@ mod subsections { pub const TERMINAL_PROMPT: keys::Boolean = keys::Boolean::new_boolean("terminalPrompt", &Gitoxide::CREDENTIALS) .with_note("This is a custom addition to provide an alternative to the respective environment variable.") .with_environment_override("GIT_TERMINAL_PROMPT"); + + /// The `gitoxide.credentials.helperStderr` key to control what happens with the credential helpers `stderr`. + /// + /// If `true`, the default, `stderr` of credential helper programs will be inherited, just like with `git`. + /// If `false`, will be suppressed completely. + pub const HELPER_STDERR: keys::Boolean = keys::Boolean::new_boolean("helperStderr", &Gitoxide::CREDENTIALS) + .with_environment_override("GIX_CREDENTIALS_HELPER_STDERR"); } impl Section for Credentials { @@ -475,7 +492,7 @@ mod subsections { } fn keys(&self) -> &[&dyn Key] { - &[&Self::TERMINAL_PROMPT] + &[&Self::TERMINAL_PROMPT, &Self::HELPER_STDERR] } fn parent(&self) -> Option<&dyn Section> { diff --git a/gix/src/repository/config/mod.rs b/gix/src/repository/config/mod.rs index 4b3b0637a15..6966e1276bc 100644 --- a/gix/src/repository/config/mod.rs +++ b/gix/src/repository/config/mod.rs @@ -88,15 +88,27 @@ impl crate::Repository { tree::{gitoxide, Key}, }; - let boolean = |key: &dyn Key| { + let pathspec_boolean = |key: &'static config::tree::keys::Boolean| { self.config .resolved .boolean("gitoxide", Some("pathspec".into()), key.name()) + .map(|value| key.enrich_error(value)) .transpose() .with_leniency(self.config.lenient_config) }; Ok(gix_command::Context { + stderr: { + let key = &gitoxide::Core::EXTERNAL_COMMAND_STDERR; + self.config + .resolved + .boolean("gitoxide", Some("core".into()), key.name()) + .map(|value| key.enrich_error(value)) + .transpose() + .with_leniency(self.config.lenient_config)? + .unwrap_or(true) + .into() + }, git_dir: self.git_dir().to_owned().into(), worktree_dir: self.work_dir().map(ToOwned::to_owned), no_replace_objects: config::shared::is_replace_refs_enabled( @@ -106,9 +118,10 @@ impl crate::Repository { )? .map(|enabled| !enabled), ref_namespace: self.refs.namespace.as_ref().map(|ns| ns.as_bstr().to_owned()), - literal_pathspecs: boolean(&gitoxide::Pathspec::LITERAL)?, - glob_pathspecs: boolean(&gitoxide::Pathspec::GLOB)?.or(boolean(&gitoxide::Pathspec::NOGLOB)?), - icase_pathspecs: boolean(&gitoxide::Pathspec::ICASE)?, + literal_pathspecs: pathspec_boolean(&gitoxide::Pathspec::LITERAL)?, + glob_pathspecs: pathspec_boolean(&gitoxide::Pathspec::GLOB)? + .or(pathspec_boolean(&gitoxide::Pathspec::NOGLOB)?), + icase_pathspecs: pathspec_boolean(&gitoxide::Pathspec::ICASE)?, }) } diff --git a/gix/tests/config/tree.rs b/gix/tests/config/tree.rs index 4a85f494943..e3a67fc4721 100644 --- a/gix/tests/config/tree.rs +++ b/gix/tests/config/tree.rs @@ -448,7 +448,7 @@ mod core { .try_into_usize(signed(-1)) .unwrap_err() .to_string(), - "The value of key \"core.deltaBaseCacheLimit\" (possibly from GITOXIDE_PACK_CACHE_MEMORY) could not be parsed as unsigned integer" + "The value of key \"core.deltaBaseCacheLimit\" (possibly from GIX_PACK_CACHE_MEMORY) could not be parsed as unsigned integer" ); assert!(Core::DELTA_BASE_CACHE_LIMIT.validate("-1".into()).is_err()); Ok(()) diff --git a/gix/tests/gix-init.rs b/gix/tests/gix-init.rs index 917c62bbac1..c4cee9deb20 100644 --- a/gix/tests/gix-init.rs +++ b/gix/tests/gix-init.rs @@ -38,8 +38,10 @@ mod with_overrides { .set("GIT_AUTHOR_EMAIL", "author email") .set("GIT_AUTHOR_DATE", default_date) .set("EMAIL", "user email") - .set("GITOXIDE_PACK_CACHE_MEMORY", "0") - .set("GITOXIDE_OBJECT_CACHE_MEMORY", "5m") + .set("GIX_PACK_CACHE_MEMORY", "0") + .set("GIX_OBJECT_CACHE_MEMORY", "5m") + .set("GIX_CREDENTIALS_HELPER_STDERR", "creds-stderr") + .set("GIX_EXTERNAL_COMMAND_STDERR", "filter-stderr") .set("GIT_SSL_CAINFO", "./env.pem") .set("GIT_SSL_VERSION", "tlsv1.3") .set("GIT_SSH_VARIANT", "ssh-variant-env") @@ -254,6 +256,8 @@ mod with_overrides { ("gitoxide.pathspec.noglob", "pathspecs-noglob"), ("gitoxide.pathspec.literal", "pathspecs-literal"), ("gitoxide.credentials.terminalPrompt", "42"), + ("gitoxide.credentials.helperStderr", "creds-stderr"), + ("gitoxide.core.externalCommandStderr", "filter-stderr"), ] { assert_eq!( config diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index 6b49746b624..6a19a9003f1 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -255,8 +255,8 @@ pub fn main() -> Result<()> { let mut engine = core::corpus::Engine::open_or_create( db, core::corpus::engine::State { - gitoxide_version: option_env!("GITOXIDE_VERSION") - .ok_or_else(|| anyhow::anyhow!("GITOXIDE_VERSION must be set in build-script"))? + gitoxide_version: option_env!("GIX_VERSION") + .ok_or_else(|| anyhow::anyhow!("GIX_VERSION must be set in build-script"))? .into(), progress, trace_to_progress: trace, diff --git a/src/plumbing/options/mod.rs b/src/plumbing/options/mod.rs index 60d3c02c2b1..892a0395b3d 100644 --- a/src/plumbing/options/mod.rs +++ b/src/plumbing/options/mod.rs @@ -5,7 +5,7 @@ use gitoxide_core as core; use gix::bstr::BString; #[derive(Debug, clap::Parser)] -#[clap(name = "gix", about = "The git underworld", version = option_env!("GITOXIDE_VERSION"))] +#[clap(name = "gix", about = "The git underworld", version = option_env!("GIX_VERSION"))] #[clap(subcommand_required = true)] #[clap(arg_required_else_help = true)] pub struct Args { diff --git a/src/porcelain/options.rs b/src/porcelain/options.rs index 32533ad890f..263779dec11 100644 --- a/src/porcelain/options.rs +++ b/src/porcelain/options.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; #[derive(Debug, clap::Parser)] -#[clap(about = "The rusty git", version = option_env!("GITOXIDE_VERSION"))] +#[clap(about = "The rusty git", version = option_env!("GIX_VERSION"))] #[clap(subcommand_required = true)] pub struct Args { /// Do not display verbose messages and progress information diff --git a/tests/tools/src/lib.rs b/tests/tools/src/lib.rs index 95f95d30a92..61b6be3a8a9 100644 --- a/tests/tools/src/lib.rs +++ b/tests/tools/src/lib.rs @@ -262,7 +262,7 @@ fn fixture_bytes_inner(path: impl AsRef, root: DirectoryRoot) -> Vec { /// In order to speed up CI and even local runs should the cache get purged, the result of each script run /// is automatically placed into a compressed _tar_ archive. /// If a script result doesn't exist, these will be checked first and extracted if present, which they are by default. -/// This behaviour can be prohibited by setting the `GITOXIDE_TEST_IGNORE_ARCHIVES` to any value. +/// This behaviour can be prohibited by setting the `GIX_TEST_IGNORE_ARCHIVES` to any value. /// /// To speed CI up, one can add these archives to the repository. It's absolutely recommended to use `gix-lfs` for that to /// not bloat the repository size. @@ -631,11 +631,11 @@ fn extract_archive( let archive_buf: Vec = { let mut buf = Vec::new(); let input_archive = std::fs::File::open(archive)?; - if std::env::var_os("GITOXIDE_TEST_IGNORE_ARCHIVES").is_some() { + if std::env::var_os("GIX_TEST_IGNORE_ARCHIVES").is_some() { return Err(std::io::Error::new( std::io::ErrorKind::Other, format!( - "Ignoring archive at '{}' as GITOXIDE_TEST_IGNORE_ARCHIVES is set.", + "Ignoring archive at '{}' as GIX_TEST_IGNORE_ARCHIVES is set.", archive.display() ), ));