diff --git a/Cargo.toml b/Cargo.toml index 38d96bfc..b3ac12ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ libc = "0.2" once_cell = "1.13" percent-encoding = "2" rand = "0.8" +semver = "1.0" sha-1 = "0.10" tee = "0.1" tempfile = "3" @@ -104,10 +105,6 @@ version = "1" default-features = false features = ["std"] -[dependencies.semver] -version = "1.0" -optional = true - [dependencies.shared_child] version = "1.0" optional = true @@ -146,7 +143,6 @@ version = "0.1" optional = true [dev-dependencies] -semver = "1.0" tempfile = "3" [profile.release] @@ -161,9 +157,9 @@ default = ["version-check"] # libcurl.so compatibility (Linux only). curl-compat = ["rustflags"] # Check and report when a new version is available. -version-check = ["semver", "shared_child"] +version-check = ["shared_child"] # Download and apply new versions. -self-update = ["semver", "shared_child", "dep:tar", "dep:xz2", "dep:zip", "windows-sys/Win32_System_Threading"] +self-update = ["shared_child", "dep:tar", "dep:xz2", "dep:zip", "windows-sys/Win32_System_Threading"] # Development features diff --git a/src/main.rs b/src/main.rs index 3c00258e..30ecb193 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3698,8 +3698,8 @@ impl FromStr for AbbrevSize { #[derive(Parser)] #[command( name = "git-cinnabar", - version=*SHORT_VERSION, - long_version=*FULL_VERSION, + version=SHORT_VERSION.as_ref(), + long_version=FULL_VERSION.as_ref(), arg_required_else_help = true, dont_collapse_args_in_usage = true, subcommand_required = true, @@ -5165,6 +5165,30 @@ pub fn get_config(name: &str) -> Option { get_config_remote(name, None) } +pub trait ConfigType: ToOwned { + fn from_os_string(value: OsString) -> Option; +} + +impl ConfigType for str { + fn from_os_string(value: OsString) -> Option { + value.into_string().ok() + } +} + +impl ConfigType for bool { + fn from_os_string(value: OsString) -> Option { + match value.as_bytes() { + b"1" | b"true" => Some(true), + b"" | b"0" | b"false" => Some(false), + _ => None, + } + } +} + +pub fn get_typed_config(name: &str) -> Option { + get_config(name).and_then(T::from_os_string) +} + pub fn get_config_remote(name: &str, remote: Option<&str>) -> Option { const PREFIX: &str = "GIT_CINNABAR_"; let mut env_key = String::with_capacity(name.len() + PREFIX.len()); @@ -5281,6 +5305,7 @@ bitflags! { const GIT_COMMIT = 0x2; const TAG = 0x4; const BRANCH = 0x8; + const TEST = 0x100; } } pub struct AllExperiments { @@ -5309,6 +5334,9 @@ static EXPERIMENTS: Lazy = Lazy::new(|| { b"branch" => { flags |= Experiments::BRANCH; } + b"test" => { + flags |= Experiments::TEST; + } s if s.starts_with(b"similarity") => { if let Some(value) = s[b"similarity".len()..].strip_prefix(b"=") { match u8::from_bytes(value) { diff --git a/src/version.rs b/src/version.rs index 0e581606..c0037714 100644 --- a/src/version.rs +++ b/src/version.rs @@ -2,11 +2,43 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +use std::borrow::{Borrow, Cow}; +use std::str::FromStr; + use once_cell::sync::Lazy; +use semver::Version; + +use crate::git::CommitId; +use crate::{experiment, get_typed_config, ConfigType, Experiments}; + +macro_rules! join { + ($s:expr) => { + Cow::Borrowed($s) + }; + ($($s:expr),+) => { + Cow::Owned(itertools::join(&[$($s),+], "")) + } +} + +macro_rules! full_version { + ($short_version:expr, $build_commit:expr, $modified:expr, $macro:ident) => { + if $build_commit.is_empty() { + $macro!($short_version) + } else { + $macro!( + $short_version, + "-", + $build_commit, + if $modified { "-modified" } else { "" } + ) + } + }; +} mod static_ { use clap::crate_version; - use concat_const::concat; + // Work around https://github.com/rust-lang/rust-analyzer/issues/8828 with `as cat`. + use concat_const::concat as cat; use git_version::git_version; #[cfg(any(feature = "version-check", feature = "self-update"))] @@ -17,7 +49,7 @@ mod static_ { args = ["--always", "--match=nothing/", "--abbrev=40", "--dirty=m"], fallback = "", ); - const MODIFIED: bool = matches!(GIT_VERSION.as_bytes().last(), Some(b'm')); + pub const MODIFIED: bool = matches!(GIT_VERSION.as_bytes().last(), Some(b'm')); pub const BUILD_COMMIT: &str = unsafe { // Subslicing is not supported in const yet. std::str::from_utf8_unchecked(std::slice::from_raw_parts( @@ -29,25 +61,67 @@ mod static_ { #[cfg(any(feature = "version-check", feature = "self-update"))] pub const BUILD_BRANCH: BuildBranch = BuildBranch::from_version(SHORT_VERSION); - #[allow(clippy::const_is_empty)] - pub const FULL_VERSION: &str = if BUILD_COMMIT.is_empty() { - SHORT_VERSION + // #[allow(clippy::const_is_empty)] + pub const FULL_VERSION: &str = full_version!(SHORT_VERSION, BUILD_COMMIT, MODIFIED, cat); +} + +fn value<'a, T: 'a + ConfigType + ?Sized, F: FnOnce(&T) -> bool>( + config: &str, + static_value: &'a T, + filter: F, +) -> Cow<'a, T> { + if let Some(value) = experiment(Experiments::TEST) + .then(|| get_typed_config::(config)) + .flatten() + .filter(|x| filter(x.borrow())) + { + Cow::Owned(value) } else { - concat!( - SHORT_VERSION, - "-", - BUILD_COMMIT, - if MODIFIED { "-modified" } else { "" } - ) + Cow::Borrowed(static_value) + } +} + +macro_rules! value { + ($config:expr, $static_value:expr) => { + value!($config, $static_value, |_| true) + }; + ($config:expr, $static_value:expr, $filter:expr) => { + Lazy::new(|| value($config, $static_value, $filter)) }; } -pub static SHORT_VERSION: Lazy<&'static str> = Lazy::new(|| static_::SHORT_VERSION); -#[cfg(any(feature = "version-check", feature = "self-update"))] -pub static BUILD_COMMIT: Lazy<&'static str> = Lazy::new(|| static_::BUILD_COMMIT); +type Value = Lazy>; + +fn is_overridden(value: &Value) -> bool { + matches!(**value, Cow::Owned(_)) +} + +pub static SHORT_VERSION: Value = + value!("version", static_::SHORT_VERSION, |v| Version::parse(v) + .is_ok()); +pub static BUILD_COMMIT: Value = value!("commit", static_::BUILD_COMMIT, |c| c.is_empty() + || CommitId::from_str(c).is_ok()); +static MODIFIED: Value = value!("modified", &static_::MODIFIED); #[cfg(any(feature = "version-check", feature = "self-update"))] -pub static BUILD_BRANCH: Lazy = Lazy::new(|| static_::BUILD_BRANCH); -pub static FULL_VERSION: Lazy<&'static str> = Lazy::new(|| static_::FULL_VERSION); +pub static BUILD_BRANCH: Lazy = Lazy::new(|| { + if is_overridden(&SHORT_VERSION) { + BuildBranch::from_version(&SHORT_VERSION) + } else { + static_::BUILD_BRANCH + } +}); +pub static FULL_VERSION: Value = Lazy::new(|| { + if is_overridden(&SHORT_VERSION) || is_overridden(&BUILD_COMMIT) || is_overridden(&MODIFIED) { + full_version!( + SHORT_VERSION.as_ref(), + BUILD_COMMIT.as_ref(), + *MODIFIED.as_ref(), + join + ) + } else { + Cow::Borrowed(static_::FULL_VERSION) + } +}); #[cfg(any(feature = "version-check", feature = "self-update"))] #[derive(PartialEq, Eq, Debug)] @@ -91,8 +165,6 @@ impl BuildBranch { #[cfg(any(feature = "version-check", feature = "self-update"))] #[test] fn test_build_branch() { - use semver::Version; - // The following tests outline the expected lifecycle. let from_version = |v| { assert!(Version::parse(v).is_ok());