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