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