diff --git a/Cargo.lock b/Cargo.lock index f879b96..b808150 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,15 +105,15 @@ checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "either" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys", @@ -121,9 +121,9 @@ dependencies = [ [[package]] name = "fs4" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21dabded2e32cd57ded879041205c60a4a4c4bab47bd0fd2fa8b01f30849f02b" +checksum = "73969b81e8bc90a3828d913dd3973d80771bfb9d7fbe1a78a79122aad456af15" dependencies = [ "rustix", "windows-sys", @@ -152,23 +152,24 @@ checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" [[package]] name = "libc" -version = "0.2.154" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "magoo" -version = "0.2.0" +version = "0.2.1" dependencies = [ "clap", "fs4", "pathdiff", + "semver", "termcolor", "thiserror", "which", @@ -182,9 +183,9 @@ checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" [[package]] name = "proc-macro2" -version = "1.0.81" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" dependencies = [ "unicode-ident", ] @@ -211,6 +212,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + [[package]] name = "strsim" version = "0.11.1" @@ -219,9 +226,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.60" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", @@ -239,18 +246,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 28fea48..aace21d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "magoo" -version = "0.2.0" +version = "0.2.1" edition = "2021" description = "A wrapper for git submodule that simplifies the workflows" repository = "https://github.com/Pistonite/magoo" @@ -20,6 +20,7 @@ exclude = [ clap = { version = "4.5.4", features = ["derive"], optional = true } fs4 = "0.8.2" pathdiff = "0.2.1" +semver = "1.0.23" termcolor = "1.4.1" thiserror = "1.0.59" which = "6.0.1" diff --git a/README.md b/README.md index 46fdbab..428d7e8 100644 --- a/README.md +++ b/README.md @@ -42,14 +42,17 @@ See https://docs.rs/magoo for more info. ## Use ![magoo](https://raw.githubusercontent.com/Pistonite/magoo/main/magoo.webp) ![magoo](https://raw.githubusercontent.com/Pistonite/magoo/main/magoo.webp) runs `git` commands using sub-processes, so you must have `git` installed on the system. -To check the version info, run +By default, ![magoo](https://raw.githubusercontent.com/Pistonite/magoo/main/magoo.webp) checks if the `git` version is supported. +To print what version is supported manually, run: ``` magoo status --git ``` -Unsupported versions might work as well, ![magoo](https://raw.githubusercontent.com/Pistonite/magoo/main/magoo.webp) just doesn't know. - -**git <=2.42.0 doesn't work due to a bug in set-branch and set-url commands** +Unsupported versions might work as well, you can let ![magoo](https://raw.githubusercontent.com/Pistonite/magoo/main/magoo.webp) know with the `--allow-unsupported` flag (note +it needs to be before the subcommand) +``` +magoo --allow-unsupported status +``` ### Add a submodule diff --git a/README.txtpp.md b/README.txtpp.md index fbfffae..fce21b4 100644 --- a/README.txtpp.md +++ b/README.txtpp.md @@ -64,16 +64,21 @@ TXTPP#include magoo.txt TXTPP#tag MAGOO TXTPP#include magoo.txt MAGOO runs `git` commands using sub-processes, so you must have `git` installed on the system. -To check the version info, run +TXTPP#tag MAGOO +TXTPP#include magoo.txt +By default, MAGOO checks if the `git` version is supported. +To print what version is supported manually, run: ``` magoo status --git ``` TXTPP#tag MAGOO TXTPP#include magoo.txt -Unsupported versions might work as well, MAGOO just doesn't know. - -**git <=2.42.0 doesn't work due to a bug in set-branch and set-url commands** +Unsupported versions might work as well, you can let MAGOO know with the `--allow-unsupported` flag (note +it needs to be before the subcommand) +``` +magoo --allow-unsupported status +``` ### Add a submodule diff --git a/src/git.rs b/src/git.rs index 43530db..720fd31 100644 --- a/src/git.rs +++ b/src/git.rs @@ -9,12 +9,10 @@ use std::time::Duration; use fs4::FileExt; -use crate::print::{self, println_info, println_verbose, println_warn}; - -/// The semver notation of the officially supported git versions -/// -/// The version is not checked at run time, since unsupported versions might work fine. -pub const SUPPORTED_GIT_VERSIONS: &str = ">=2.43.0"; +use crate::print::{ + self, println_error, println_hint, println_info, println_verbose, println_warn, +}; +use crate::version; /// Context for running git commands pub struct GitContext { @@ -57,14 +55,31 @@ impl GitContext { Guard::new(lock_path) } - /// Print the supported git version info and current git version into - pub fn print_version_info(&self) -> Result<(), GitError> { - println_info!( - "The officially supported git versions are: {}", - super::SUPPORTED_GIT_VERSIONS - ); - println_info!("Your `git --version` is:"); - self.run_git_command(&["--version"], true)?; + /// Check if the version is supported. If print is true, it will print the info when the + /// version is supported. Otherwise only print if it's not supported + pub fn check_version(&self, print: bool) -> Result<(), GitError> { + let out = self.run_git_command(&["--version"], false)?.join(""); + let version = version::parse_git_version(&out).ok_or_else(|| { + GitError::UnsupportedVersion("nnable to parse git version".to_string()) + })?; + if !version::is_supported(&version) { + println_error!("Magoo does not support your git version!"); + println_error!("Your version is: {}", version); + println_hint!( + "Supported versions are: {}", + version::get_supported_versions() + ); + println_hint!("Please upgrade your git to a supported version or use `magoo --allow-unsupported COMMAND`"); + return Err(GitError::UnsupportedVersion(version.to_string())); + } + if print { + println_info!("Magoo supports your git version."); + println_info!("Your version is: {}", version); + println_info!( + "Supported versions are: {}", + version::get_supported_versions() + ); + } Ok(()) } @@ -599,6 +614,9 @@ pub enum GitError { #[error("fix the issues above and try again.")] NeedFix(bool /* should show fatal */), + + #[error("unsupported git version: {0}")] + UnsupportedVersion(String), } /// Helper trait to canonicalize a path and return a [`GitError`] if failed diff --git a/src/lib.rs b/src/lib.rs index 7f7ac86..6687fe9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,7 +36,7 @@ //! // don't need this if you don't need output to stdout //! command.set_print_options(); //! // runs `magoo status --git` in the current directory -//! command.run("."); //.unwrap(); +//! command.run(".", &Default::default()); //.unwrap(); //! ``` //! #### Use `clap` to parse arguments //! ```rust @@ -61,6 +61,7 @@ //! delete: false, //! }), //! dir: "my/repo".to_string(), +//! common: Default::default(), //! }); //! //! magoo.set_print_options(); @@ -71,12 +72,12 @@ //! pub mod git; -pub use git::SUPPORTED_GIT_VERSIONS; use git::{GitContext, GitError}; pub mod print; pub mod status; pub mod submodule; +pub mod version; use status::Status; use crate::print::{println_error, println_hint, println_info, println_verbose, println_warn}; @@ -95,12 +96,15 @@ pub struct Magoo { /// Set the working directory of commands. Useful if not running inside a git repository. #[cfg_attr(feature = "cli", clap(long, short('C'), default_value(".")))] pub dir: String, + + #[cfg_attr(feature = "cli", clap(flatten))] + pub common: OtherOptions, } impl Magoo { /// Run the command pub fn run(&self) -> Result<(), GitError> { - self.subcmd.run(&self.dir) + self.subcmd.run(&self.dir, &self.common) } /// Apply the print options @@ -141,19 +145,19 @@ impl Command { } /// Run the command in the given directory. - pub fn run(&self, dir: &str) -> Result<(), GitError> { + pub fn run(&self, dir: &str, common: &OtherOptions) -> Result<(), GitError> { match self { Command::Status(cmd) => { - cmd.run(dir)?; + cmd.run(dir, common)?; } Command::Install(cmd) => { - cmd.run(dir)?; + cmd.run(dir, common)?; } Command::Update(cmd) => { - cmd.run(dir)?; + cmd.run(dir, common)?; } Command::Remove(cmd) => { - cmd.run(dir)?; + cmd.run(dir, common)?; } } @@ -165,7 +169,7 @@ impl Command { #[derive(Debug, Default, Clone, PartialEq)] #[cfg_attr(feature = "cli", derive(clap::Parser))] pub struct StatusCommand { - /// Show the current git version and if it is supported + /// Show the current git version #[cfg_attr(feature = "cli", clap(long))] pub git: bool, @@ -202,13 +206,16 @@ impl StatusCommand { } /// Run the command and return the status as a [`Status`] struct. - pub fn run(&self, dir: &str) -> Result { + pub fn run(&self, dir: &str, common: &OtherOptions) -> Result { let context = GitContext::try_from(dir)?; - let _guard = context.lock()?; if self.git { - context.print_version_info()?; + context.check_version(true)?; return Ok(Status::default()); } + if !common.allow_unsupported { + context.check_version(false)?; + } + let _guard = context.lock()?; let mut status = Status::read_from(&context)?; let mut flat_status = status.flattened_mut(); @@ -298,8 +305,11 @@ impl InstallCommand { } /// Run the command in the given directory - pub fn run(&self, dir: &str) -> Result<(), GitError> { + pub fn run(&self, dir: &str, common: &OtherOptions) -> Result<(), GitError> { let context = GitContext::try_from(dir)?; + if !common.allow_unsupported { + context.check_version(false)?; + } let _guard = context.lock()?; let mut status = Status::read_from(&context)?; @@ -377,8 +387,11 @@ impl UpdateCommand { } /// Run the command in the given directory - pub fn run(&self, dir: &str) -> Result<(), GitError> { + pub fn run(&self, dir: &str, common: &OtherOptions) -> Result<(), GitError> { let context = GitContext::try_from(dir)?; + if !common.allow_unsupported { + context.check_version(false)?; + } let _guard = context.lock()?; match &self.name { @@ -487,8 +500,11 @@ impl RemoveCommand { } /// Run the command in the given directory - pub fn run(&self, dir: &str) -> Result<(), GitError> { + pub fn run(&self, dir: &str, common: &OtherOptions) -> Result<(), GitError> { let context = GitContext::try_from(dir)?; + if !common.allow_unsupported { + context.check_version(false)?; + } let _guard = context.lock()?; let name = &self.name; @@ -583,3 +599,15 @@ impl PrintOptions { print::set_options(self.verbose, self.quiet, self.color); } } + +/// Other common options +#[derive(Debug, Default, Clone, PartialEq)] +#[cfg_attr(feature = "cli", derive(clap::Parser))] +pub struct OtherOptions { + /// Allow unsupported git versions + /// + /// This could lead to unexpected behavior or make you vulnerable to security issues. Please + /// use with caution. + #[cfg_attr(feature = "cli", clap(long))] + pub allow_unsupported: bool, +} diff --git a/src/version.rs b/src/version.rs new file mode 100644 index 0000000..a821e50 --- /dev/null +++ b/src/version.rs @@ -0,0 +1,73 @@ +//! Check git version + +use semver::{Version, VersionReq}; + +/// Parse the output of `git --version` to a semver +pub fn parse_git_version(version: &str) -> Option { + let version = version.trim().trim_start_matches("git version "); + let mut parts = version.split(".windows."); + let cleaned = parts.next().unwrap_or("").trim(); + Version::parse(cleaned).ok() +} + +/// The semver notation of the officially supported git versions +pub const SUPPORTED_GIT_VERSIONS: [&str; 3] = [">=2.45.1", "~2.44.1", "~2.43.4"]; + +/// Get string representation of supported git versions +pub fn get_supported_versions() -> String { + SUPPORTED_GIT_VERSIONS.join(", ") +} + +pub fn is_supported(version: &Version) -> bool { + SUPPORTED_GIT_VERSIONS + .iter() + .any(|v| VersionReq::parse(v).unwrap().matches(version)) +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_parse_git_version() { + assert_eq!( + parse_git_version("git version 2.40.0"), + Some(Version::new(2, 40, 0)) + ); + assert_eq!( + parse_git_version("git version 2.30.1"), + Some(Version::new(2, 30, 1)) + ); + assert_eq!( + parse_git_version("git version 2.43.0.windows.1"), + Some(Version::new(2, 43, 0)) + ); + assert_eq!(parse_git_version("gi version 2.43.0.windows.1"), None); + } + + #[test] + fn supported_version_parses() { + for version in SUPPORTED_GIT_VERSIONS { + assert!(VersionReq::parse(version).is_ok()); + } + assert!(is_supported( + &parse_git_version("git version 2.45.1").unwrap() + )); + assert!(!is_supported( + &parse_git_version("git version 2.45.0").unwrap() + )); + assert!(is_supported( + &parse_git_version("git version 2.44.1").unwrap() + )); + assert!(!is_supported( + &parse_git_version("git version 2.44.0").unwrap() + )); + assert!(is_supported( + &parse_git_version("git version 2.43.4").unwrap() + )); + assert!(!is_supported( + &parse_git_version("git version 2.43.3").unwrap() + )); + } +}