Skip to main content

gitversion_rs/cli/
mod.rs

1//! clap-based command-line interface.
2//!
3//! Ports the main options from the original `GitVersion.App/ArgumentParser.cs`.
4
5use crate::config::{
6    DeploymentMode, GitVersionConfiguration, IncrementStrategy, SemanticVersionFormat,
7};
8use clap::{CommandFactory, Parser, ValueEnum};
9use rust_i18n::t;
10use std::path::PathBuf;
11
12/// Output format.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
14pub enum OutputFormat {
15    Json,
16    /// Corresponds to the original `/output file`: writes JSON to `--outputfile` (same rendering as Json).
17    File,
18    DotEnv,
19    BuildServer,
20}
21
22/// Log verbosity level. Corresponds to the original `Verbosity`.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
24pub enum Verbosity {
25    Quiet,
26    Minimal,
27    Normal,
28    Verbose,
29    Diagnostic,
30}
31
32impl Verbosity {
33    /// Convert to a `log` crate level filter.
34    pub fn to_level(self) -> log::LevelFilter {
35        match self {
36            Verbosity::Quiet => log::LevelFilter::Error,
37            Verbosity::Minimal => log::LevelFilter::Warn,
38            Verbosity::Normal => log::LevelFilter::Info,
39            Verbosity::Verbose => log::LevelFilter::Debug,
40            Verbosity::Diagnostic => log::LevelFilter::Trace,
41        }
42    }
43}
44
45// Help/about strings default to English in source; `localized_command()` overrides them at
46// runtime using `cli.about` / `cli.help.<id>` keys. Struct doc comments (`///`) become
47// `long_about` which shadows the `about` override, so plain comments (`//`) are used here.
48#[derive(Debug, Parser)]
49#[command(
50    name = "gitversion-rs",
51    version,
52    about = "Calculate a semantic version from Git history (GitVersion Rust port)"
53)]
54pub struct Cli {
55    /// Repository path (directory containing `.git`). Defaults to the current directory.
56    #[arg(default_value = ".")]
57    pub path: PathBuf,
58
59    /// Same as path but position-independent (upstream `/targetpath`).
60    #[arg(long = "targetpath", value_name = "DIR")]
61    pub target_path: Option<PathBuf>,
62
63    // nofetch/nonormalize/allowshallow are accepted for CLI compatibility with the original,
64    // but have no effect in this port because fetch/normalize are not performed.
65    /// Disable fetch (no-op: this port does not fetch).
66    #[arg(long)]
67    pub nofetch: bool,
68    /// Disable normalization (no-op).
69    #[arg(long)]
70    pub nonormalize: bool,
71    /// Disable disk cache read/write (`<.git>/gitversion_cache`).
72    #[arg(long)]
73    pub nocache: bool,
74    /// Allow shallow clone (no-op: gix reads shallow repos too).
75    #[arg(long)]
76    pub allowshallow: bool,
77
78    /// Output format (json, dot-env, build-server). May be repeated.
79    #[arg(long, value_enum, default_value = "json")]
80    pub output: Vec<OutputFormat>,
81
82    /// Output file path (writes the result to a file when set).
83    #[arg(long = "outputfile")]
84    pub output_file: Option<PathBuf>,
85
86    /// Print a single variable only (e.g. -v SemVer).
87    #[arg(long = "showvariable", short = 'v')]
88    pub show_variable: Option<String>,
89
90    /// Print using a format string (e.g. --format "{Major}.{Minor}").
91    #[arg(long)]
92    pub format: Option<String>,
93
94    /// Config file path.
95    #[arg(long)]
96    pub config: Option<PathBuf>,
97
98    /// Print the effective config as YAML and exit.
99    #[arg(long = "showconfig")]
100    pub show_config: bool,
101
102    /// Inline config override (key=value). May be repeated.
103    #[arg(long = "overrideconfig")]
104    pub override_config: Vec<String>,
105
106    /// Branch to compute for (instead of the current checkout).
107    #[arg(long, short = 'b')]
108    pub branch: Option<String>,
109
110    /// Output language (ko/en/ja/zh). Falls back to LANG/LC_ALL when omitted.
111    #[arg(long, value_name = "LANG")]
112    pub lang: Option<String>,
113
114    /// Log verbosity.
115    #[arg(long, value_enum, default_value = "normal")]
116    pub verbosity: Verbosity,
117
118    /// Write log output to a file (upstream `/l`), or `console` for stderr.
119    /// Logs append; stdout stays clean.
120    #[arg(long = "log", short = 'l', value_name = "FILE")]
121    pub log_file: Option<PathBuf>,
122
123    /// Diagnostic mode (Trace logging).
124    #[arg(long)]
125    pub diag: bool,
126
127    /// Update AssemblyInfo files (recursive search when no file is given).
128    #[arg(long = "updateassemblyinfo", num_args = 0.., value_name = "FILE")]
129    pub update_assembly_info: Option<Vec<String>>,
130
131    /// Create the AssemblyInfo file if missing (with updateassemblyinfo).
132    #[arg(long = "ensureassemblyinfo")]
133    pub ensure_assembly_info: bool,
134
135    /// Update version elements in project files (.csproj etc.; recursive when no file is given).
136    #[arg(long = "updateprojectfiles", num_args = 0.., value_name = "FILE")]
137    pub update_project_files: Option<Vec<String>>,
138
139    /// Create GitVersion_WixVersion.wxi.
140    #[arg(long = "updatewixversionfile")]
141    pub update_wix_version_file: bool,
142
143    /// Update the version in package manifests (package.json/Cargo.toml/pyproject.toml).
144    /// Recursive search when no file is given.
145    #[arg(long = "updatepackagefiles", num_args = 0.., value_name = "FILE")]
146    pub update_package_files: Option<Vec<String>>,
147
148    /// Remote git repository URL (clone then compute when set). Requires `--branch`.
149    #[arg(long)]
150    pub url: Option<String>,
151
152    /// Remote auth username (with `--url`).
153    #[arg(long = "username", short = 'u')]
154    pub username: Option<String>,
155
156    /// Remote auth password (with `--url`).
157    #[arg(long = "password", short = 'p')]
158    pub password: Option<String>,
159
160    /// Commit ID to inspect (latest on the branch when omitted). With `--url`.
161    #[arg(long = "commit", short = 'c')]
162    pub commit: Option<String>,
163
164    /// Dynamic clone location (default: a temp directory).
165    #[arg(long = "dynamicRepoLocation")]
166    pub dynamic_repo_location: Option<PathBuf>,
167
168    /// prepare command to run after computing (version variables exposed as GitVersion_* env and {Var}).
169    #[arg(long)]
170    pub exec: Option<String>,
171
172    /// Version-modifying command. Its stdout is applied as next-version and recomputed.
173    #[arg(long = "exec-version")]
174    pub exec_version: Option<String>,
175
176    /// Print exec hooks without actually running them.
177    #[arg(long = "dry-run")]
178    pub dry_run: bool,
179
180    /// Launch the interactive Ratatui TUI.
181    #[arg(long)]
182    pub tui: bool,
183}
184
185/// Build a clap Command with `about` and per-argument `help` overridden by `t!` for the current locale.
186/// If a `cli.about` or `cli.help.<arg_id>` key resolves to a translated string, it replaces the
187/// English source doc; if the key is returned as-is (no translation), the English source doc is kept.
188/// Must be called after the locale is set (before argument parsing).
189pub fn localized_command() -> clap::Command {
190    Cli::command()
191        .about(t!("cli.about").to_string())
192        .mut_args(|arg| {
193            let key = format!("cli.help.{}", arg.get_id());
194            let val = t!(key.as_str()).to_string();
195            if val == key {
196                arg
197            } else {
198                arg.help(val)
199            }
200        })
201}
202
203/// Apply `key=value` overrideconfig entries to the configuration.
204pub fn apply_overrides(config: &mut GitVersionConfiguration, overrides: &[String]) {
205    for raw in overrides {
206        let Some((key, value)) = raw.split_once('=') else {
207            log::warn!("{}", t!("cli.override_invalid", entry = raw));
208            continue;
209        };
210        let key = key.trim();
211        let value = value.trim().to_string();
212        match key {
213            "tag-prefix" => config.tag_prefix = Some(value),
214            "next-version" => config.next_version = Some(value),
215            "label" => config.label = Some(value),
216            "commit-date-format" => config.commit_date_format = Some(value),
217            "major-version-bump-message" => config.major_version_bump_message = Some(value),
218            "minor-version-bump-message" => config.minor_version_bump_message = Some(value),
219            "patch-version-bump-message" => config.patch_version_bump_message = Some(value),
220            "no-bump-message" => config.no_bump_message = Some(value),
221            "tag-pre-release-weight" => {
222                if let Ok(n) = value.parse() {
223                    config.tag_pre_release_weight = Some(n);
224                }
225            }
226            "update-build-number" => config.update_build_number = value.parse().ok(),
227            "increment" => config.increment = parse_increment(&value),
228            "mode" => config.mode = parse_mode(&value),
229            "semantic-version-format" => {
230                config.semantic_version_format = match value.to_lowercase().as_str() {
231                    "loose" => Some(SemanticVersionFormat::Loose),
232                    _ => Some(SemanticVersionFormat::Strict),
233                }
234            }
235            other => log::warn!("{}", t!("cli.override_unsupported", key = other)),
236        }
237    }
238}
239
240fn parse_increment(v: &str) -> Option<IncrementStrategy> {
241    Some(match v.to_lowercase().as_str() {
242        "major" => IncrementStrategy::Major,
243        "minor" => IncrementStrategy::Minor,
244        "patch" => IncrementStrategy::Patch,
245        "none" => IncrementStrategy::None,
246        "inherit" => IncrementStrategy::Inherit,
247        _ => return None,
248    })
249}
250
251fn parse_mode(v: &str) -> Option<DeploymentMode> {
252    Some(match v.to_lowercase().as_str() {
253        "continuousdelivery" => DeploymentMode::ContinuousDelivery,
254        "continuousdeployment" => DeploymentMode::ContinuousDeployment,
255        "manualdeployment" => DeploymentMode::ManualDeployment,
256        _ => return None,
257    })
258}