//! Git version detection. forge requires git 2.38.0 or newer. use crate::command::{GitCommand, GitEnv}; use crate::error::{Error, Result}; /// Parses `git version` output such as `git version 2.56.0` or `git version 2.39.2.windows.1`. pub fn parse_git_version(output: &str) -> Option<(u32, u32, u32)> { let token = output .split_whitespace() .find(|t| t.starts_with(|c: char| c.is_ascii_digit()))?; let mut parts = token.split('.'); let major = leading_number(parts.next()?)?; let minor = leading_number(parts.next()?)?; let patch = parts.next().and_then(leading_number).unwrap_or(0); Some((major, minor, patch)) } /// The number at the start of `s`, so `0-rc1` yields 0. fn leading_number(s: &str) -> Option { let digits: String = s.chars().take_while(|c| c.is_ascii_digit()).collect(); digits.parse().ok() } fn fmt_version((major, minor, patch): (u32, u32, u32)) -> String { format!("{major}.{minor}.{patch}") } /// Parses `git version` into (major, minor, patch). pub async fn git_version(env: &GitEnv) -> Result<(u32, u32, u32)> { let out = GitCommand::new(env, "version").output().await?; let text = String::from_utf8_lossy(&out); parse_git_version(&text).ok_or_else(|| { Error::Unavailable(format!("cannot parse git version from {:?}", text.trim())) }) } /// Fails with `Error::TooOld` if git is older than `min`. pub async fn require_min_version(env: &GitEnv, min: (u32, u32, u32)) -> Result<()> { let found = git_version(env).await?; if found < min { return Err(Error::TooOld { found: fmt_version(found), required: fmt_version(min), }); } Ok(()) }