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                &key_value,
115                aggregate_by.map(ReductionFunc::from),
116                &combined_patterns,
117                template_config,
118                show_epochs,
119                show_changes,
120            )
121        }
122        Commands::Audit {
123            commit,
124            measurement,
125            report_history,
126            selectors,
127            min_measurements,
128            aggregate_by,
129            sigma,
130            dispersion_method,
131            filter,
132            no_change_point_warning,
133        } => {
134            let commit = commit.as_deref().unwrap_or("HEAD");
135            // Validate that at least one of measurement or filter is provided
136            // (clap's required_unless_present should handle this, but double-check for safety)
137            if measurement.is_empty() && filter.is_empty() {
138                Cli::command()
139                    .error(
140                        clap::error::ErrorKind::MissingRequiredArgument,
141                        "At least one of --measurement or --filter must be provided",
142                    )
143                    .exit()
144            }
145
146            // Validate max_count vs min_measurements if min_measurements is specified via CLI
147            if let Some(min_count) = min_measurements {
148                if report_history.max_count < min_count.into() {
149                    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()
150                }
151            }
152
153            // Combine measurements (as exact matches) and filter patterns into unified regex patterns
154            let combined_patterns =
155                crate::filter::combine_measurements_and_filters(&measurement, &filter);
156
157            audit::audit_multiple(
158                commit,
159                report_history.max_count,
160                min_measurements,
161                &selectors,
162                aggregate_by.map(ReductionFunc::from),
163                sigma,
164                dispersion_method.map(crate::stats::DispersionMethod::from),
165                &combined_patterns,
166                no_change_point_warning,
167            )
168        }
169        Commands::BumpEpoch { measurements } => {
170            for measurement in measurements {
171                bump_epoch(&measurement)?;
172            }
173            Ok(())
174        }
175        Commands::Prune {} => prune(),
176        Commands::Status { detailed } => status::show_status(detailed),
177        Commands::Reset { dry_run, force } => reset::reset_measurements(dry_run, force),
178        Commands::Remove {
179            older_than,
180            no_prune,
181            dry_run,
182        } => remove_measurements_from_commits(older_than, !no_prune, dry_run),
183        Commands::ListCommits {} => {
184            let commits = list_commits_with_measurements()?;
185            for commit in commits {
186                println!("{}", commit);
187            }
188            Ok(())
189        }
190        Commands::Size {
191            detailed,
192            format,
193            disk_size,
194            include_objects,
195        } => size::calculate_measurement_size(detailed, format, disk_size, include_objects),
196        Commands::Config {
197            list,
198            detailed,
199            format,
200            validate,
201            measurement,
202        } => {
203            if list {
204                config_cmd::list_config(detailed, format, validate, measurement)
205            } else {
206                // For now, --list is required. In the future, this could support
207                // other config operations like --get, --set, etc.
208                anyhow::bail!("config command requires --list flag (try: git perf config --list)");
209            }
210        }
211    }
212}