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