Skip to main content

git_perf/
cli.rs

1use anyhow::Result;
2use clap::CommandFactory;
3use clap::{error::ErrorKind::ArgumentConflict, Parser};
4use env_logger::Env;
5use log::Level;
6
7use crate::audit;
8use crate::basic_measure::measure;
9use crate::config::bump_epoch;
10use crate::config_cmd;
11use crate::git::git_interop::check_git_version;
12use crate::git::git_interop::{list_commits_with_measurements, prune, pull, push};
13use crate::import::{handle_import, ImportOptions};
14use crate::measurement_storage::{add_to_commit as add, remove_measurements_from_commits};
15use crate::reporting::report;
16use crate::reset;
17use crate::size;
18use crate::stats::ReductionFunc;
19use crate::status;
20use git_perf_cli_types::{Cli, Commands};
21
22pub fn handle_calls() -> Result<()> {
23    let cli = Cli::parse();
24    let logger_level = match cli.verbose {
25        0 => Level::Warn,
26        1 => Level::Info,
27        2 => Level::Debug,
28        _ => Level::Trace,
29    };
30    env_logger::Builder::from_env(Env::default().default_filter_or(logger_level.as_str())).init();
31
32    check_git_version()?;
33
34    match cli.command {
35        Commands::Measure {
36            repetitions,
37            measurement,
38            commit,
39            command,
40        } => {
41            let commit = commit.as_deref().unwrap_or("HEAD");
42            measure(
43                commit,
44                &measurement.name,
45                repetitions,
46                &command,
47                &measurement.key_value,
48            )
49        }
50        Commands::Add {
51            value,
52            measurement,
53            commit,
54        } => {
55            let commit = commit.as_deref().unwrap_or("HEAD");
56            add(commit, &measurement.name, value, &measurement.key_value)
57        }
58        Commands::Import {
59            format,
60            file,
61            commit,
62            prefix,
63            metadata,
64            filter,
65            dry_run,
66            verbose,
67        } => {
68            let commit = commit.as_deref().unwrap_or("HEAD").to_string();
69            handle_import(ImportOptions {
70                commit,
71                format,
72                file,
73                prefix,
74                metadata,
75                filter,
76                dry_run,
77                verbose,
78            })
79        }
80        Commands::Push { remote } => push(None, remote.as_deref()),
81        Commands::Pull {} => pull(None),
82        Commands::Report {
83            commit,
84            output,
85            separate_by,
86            report_history,
87            measurement,
88            key_value,
89            aggregate_by,
90            filter,
91            template,
92            custom_css,
93            title,
94            show_epochs,
95            show_changes,
96        } => {
97            let commit = commit.as_deref().unwrap_or("HEAD");
98
99            // Combine measurements (as exact matches) and filter patterns into unified regex patterns
100            let combined_patterns =
101                crate::filter::combine_measurements_and_filters(&measurement, &filter);
102
103            let template_config = crate::reporting::ReportTemplateConfig {
104                template_path: template,
105                custom_css_path: custom_css,
106                title,
107            };
108
109            report(
110                commit,
111                output,
112                separate_by,
113                report_history.max_count,
114                report_history.since.as_deref(),
115                report_history.until.as_deref(),
116                &key_value,
117                aggregate_by.map(ReductionFunc::from),
118                &combined_patterns,
119                template_config,
120                show_epochs,
121                show_changes,
122            )
123        }
124        Commands::Audit {
125            commit,
126            measurement,
127            report_history,
128            selectors,
129            separate_by,
130            min_measurements,
131            aggregate_by,
132            sigma,
133            dispersion_method,
134            filter,
135            no_change_point_warning,
136        } => {
137            let commit = commit.as_deref().unwrap_or("HEAD");
138            // Validate that at least one of measurement or filter is provided
139            // (clap's required_unless_present should handle this, but double-check for safety)
140            if measurement.is_empty() && filter.is_empty() {
141                Cli::command()
142                    .error(
143                        clap::error::ErrorKind::MissingRequiredArgument,
144                        "At least one of --measurement or --filter must be provided",
145                    )
146                    .exit()
147            }
148
149            // Validate max_count vs min_measurements if min_measurements is specified via CLI
150            if let Some(min_count) = min_measurements {
151                if report_history.max_count < min_count.into() {
152                    Cli::command().error(ArgumentConflict, format!("The minimal number of measurements ({}) cannot be more than the maximum number of measurements ({})", min_count, report_history.max_count)).exit()
153                }
154            }
155
156            // Combine measurements (as exact matches) and filter patterns into unified regex patterns
157            let combined_patterns =
158                crate::filter::combine_measurements_and_filters(&measurement, &filter);
159
160            audit::audit_multiple(
161                commit,
162                report_history.max_count,
163                report_history.since.as_deref(),
164                report_history.until.as_deref(),
165                min_measurements,
166                &selectors,
167                aggregate_by.map(ReductionFunc::from),
168                sigma,
169                dispersion_method.map(crate::stats::DispersionMethod::from),
170                &combined_patterns,
171                &separate_by,
172                no_change_point_warning,
173            )
174        }
175        Commands::BumpEpoch { measurements } => {
176            for measurement in measurements {
177                bump_epoch(&measurement)?;
178            }
179            Ok(())
180        }
181        Commands::Prune {} => prune(),
182        Commands::Status { detailed } => status::show_status(detailed),
183        Commands::Reset { dry_run, force } => reset::reset_measurements(dry_run, force),
184        Commands::Remove {
185            older_than,
186            no_prune,
187            dry_run,
188        } => remove_measurements_from_commits(older_than, !no_prune, dry_run),
189        Commands::ListCommits {} => {
190            let commits = list_commits_with_measurements()?;
191            for commit in commits {
192                println!("{}", commit);
193            }
194            Ok(())
195        }
196        Commands::Size {
197            detailed,
198            format,
199            disk_size,
200            include_objects,
201        } => size::calculate_measurement_size(detailed, format, disk_size, include_objects),
202        Commands::Config {
203            list,
204            detailed,
205            format,
206            validate,
207            measurement,
208        } => {
209            if list {
210                config_cmd::list_config(detailed, format, validate, measurement)
211            } else {
212                // For now, --list is required. In the future, this could support
213                // other config operations like --get, --set, etc.
214                anyhow::bail!("config command requires --list flag (try: git perf config --list)");
215            }
216        }
217    }
218}