using System.Text.RegularExpressions; using ClaudeDo.Data.Environment; using ClaudeDo.Installer.Core; using ClaudeDo.Installer.Core.Interfaces; namespace ClaudeDo.Installer.Checks; /// Without git, no task can run (worktrees) — blocking. public sealed class GitCheck : IEnvironmentCheck { public const string CheckId = "git"; private static readonly Regex VersionPattern = new(@"\d+(\.\d+){1,3}", RegexOptions.Compiled); private readonly IProcessRunner _processRunner; private readonly string? _pathOverride; private readonly string? _pathExtOverride; public GitCheck(IProcessRunner processRunner, string? pathOverride = null, string? pathExtOverride = null) { _processRunner = processRunner; _pathOverride = pathOverride; _pathExtOverride = pathExtOverride; } public string Id => CheckId; public CheckSeverity Severity => CheckSeverity.Error; public async Task RunAsync(InstallContext ctx, CancellationToken ct) { var resolved = ExecutableResolver.Resolve("git", _pathOverride, _pathExtOverride); if (resolved is null) { return CheckResult.Fail(Id, Severity, "checks.git.title", "git was not found on PATH.", "checks.git.hint", "https://git-scm.com/downloads"); } var (fileName, arguments) = ProcessCommand.For(resolved, "--version"); var (exitCode, output) = await _processRunner.RunAsync(fileName, arguments, null, ct); if (exitCode != 0) { return CheckResult.Fail(Id, Severity, "checks.git.title", $"'{resolved.Path}' exited with code {exitCode}.", "checks.git.hint", "https://git-scm.com/downloads", output); } var version = VersionPattern.Match(output) is { Success: true } match ? match.Value : output.Trim(); return CheckResult.Ok(Id, Severity, "checks.git.title", $"{resolved.Path} — {version}"); } }