Show More
@@ -1,191 +1,179 b'' | |||||
1 | extern crate log; |
|
1 | extern crate log; | |
2 | use crate::ui::Ui; |
|
2 | use crate::ui::Ui; | |
3 | use clap::App; |
|
3 | use clap::App; | |
4 | use clap::AppSettings; |
|
4 | use clap::AppSettings; | |
5 | use clap::Arg; |
|
5 | use clap::Arg; | |
6 | use clap::ArgMatches; |
|
6 | use clap::ArgMatches; | |
7 | use format_bytes::format_bytes; |
|
7 | use format_bytes::format_bytes; | |
8 | use hg::config::Config; |
|
8 | use hg::config::Config; | |
9 | use hg::repo::{Repo, RepoError}; |
|
9 | use hg::repo::{Repo, RepoError}; | |
10 | use std::path::{Path, PathBuf}; |
|
10 | use std::path::{Path, PathBuf}; | |
11 |
|
11 | |||
12 | mod blackbox; |
|
12 | mod blackbox; | |
13 | mod error; |
|
13 | mod error; | |
14 | mod exitcode; |
|
14 | mod exitcode; | |
15 | mod ui; |
|
15 | mod ui; | |
16 | use error::CommandError; |
|
16 | use error::CommandError; | |
17 |
|
17 | |||
18 | fn add_global_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { |
|
|||
19 | app.arg( |
|
|||
20 | Arg::with_name("repository") |
|
|||
21 | .help("repository root directory") |
|
|||
22 | .short("-R") |
|
|||
23 | .long("--repository") |
|
|||
24 | .value_name("REPO") |
|
|||
25 | .takes_value(true), |
|
|||
26 | ) |
|
|||
27 | .arg( |
|
|||
28 | Arg::with_name("config") |
|
|||
29 | .help("set/override config option (use 'section.name=value')") |
|
|||
30 | .long("--config") |
|
|||
31 | .value_name("CONFIG") |
|
|||
32 | .takes_value(true) |
|
|||
33 | // Ok: `--config section.key1=val --config section.key2=val2` |
|
|||
34 | .multiple(true) |
|
|||
35 | // Not ok: `--config section.key1=val section.key2=val2` |
|
|||
36 | .number_of_values(1), |
|
|||
37 | ) |
|
|||
38 | } |
|
|||
39 |
|
||||
40 | fn main_with_result( |
|
18 | fn main_with_result( | |
41 | ui: &ui::Ui, |
|
19 | ui: &ui::Ui, | |
42 | process_start_time: &blackbox::ProcessStartTime, |
|
20 | process_start_time: &blackbox::ProcessStartTime, | |
43 | ) -> Result<(), CommandError> { |
|
21 | ) -> Result<(), CommandError> { | |
44 | env_logger::init(); |
|
22 | env_logger::init(); | |
45 | let app = App::new("rhg") |
|
23 | let app = App::new("rhg") | |
46 | .setting(AppSettings::AllowInvalidUtf8) |
|
24 | .global_setting(AppSettings::AllowInvalidUtf8) | |
47 | .setting(AppSettings::SubcommandRequired) |
|
25 | .setting(AppSettings::SubcommandRequired) | |
48 | .setting(AppSettings::VersionlessSubcommands) |
|
26 | .setting(AppSettings::VersionlessSubcommands) | |
|
27 | .arg( | |||
|
28 | Arg::with_name("repository") | |||
|
29 | .help("repository root directory") | |||
|
30 | .short("-R") | |||
|
31 | .long("--repository") | |||
|
32 | .value_name("REPO") | |||
|
33 | .takes_value(true) | |||
|
34 | // Both ok: `hg -R ./foo log` or `hg log -R ./foo` | |||
|
35 | .global(true), | |||
|
36 | ) | |||
|
37 | .arg( | |||
|
38 | Arg::with_name("config") | |||
|
39 | .help("set/override config option (use 'section.name=value')") | |||
|
40 | .long("--config") | |||
|
41 | .value_name("CONFIG") | |||
|
42 | .takes_value(true) | |||
|
43 | .global(true) | |||
|
44 | // Ok: `--config section.key1=val --config section.key2=val2` | |||
|
45 | .multiple(true) | |||
|
46 | // Not ok: `--config section.key1=val section.key2=val2` | |||
|
47 | .number_of_values(1), | |||
|
48 | ) | |||
49 | .version("0.0.1"); |
|
49 | .version("0.0.1"); | |
50 | let app = add_global_args(app); |
|
|||
51 | let app = add_subcommand_args(app); |
|
50 | let app = add_subcommand_args(app); | |
52 |
|
51 | |||
53 | let matches = app.clone().get_matches_safe()?; |
|
52 | let matches = app.clone().get_matches_safe()?; | |
54 |
|
53 | |||
55 | let (subcommand_name, subcommand_matches) = matches.subcommand(); |
|
54 | let (subcommand_name, subcommand_matches) = matches.subcommand(); | |
56 | let run = subcommand_run_fn(subcommand_name) |
|
55 | let run = subcommand_run_fn(subcommand_name) | |
57 | .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired"); |
|
56 | .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired"); | |
58 | let subcommand_args = subcommand_matches |
|
57 | let subcommand_args = subcommand_matches | |
59 | .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired"); |
|
58 | .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired"); | |
60 |
|
59 | |||
61 | // Global arguments can be in either based on e.g. `hg -R ./foo log` v.s. |
|
60 | let config_args = matches | |
62 | // `hg log -R ./foo` |
|
61 | .values_of_os("config") | |
63 | let value_of_global_arg = |name| { |
|
62 | // Turn `Option::None` into an empty iterator: | |
64 | subcommand_args |
|
63 | .into_iter() | |
65 | .value_of_os(name) |
|
64 | .flatten() | |
66 | .or_else(|| matches.value_of_os(name)) |
|
|||
67 | }; |
|
|||
68 | // For arguments where multiple occurences are allowed, return a |
|
|||
69 | // possibly-iterator of all values. |
|
|||
70 | let values_of_global_arg = |name: &str| { |
|
|||
71 | let a = matches.values_of_os(name).into_iter().flatten(); |
|
|||
72 | let b = subcommand_args.values_of_os(name).into_iter().flatten(); |
|
|||
73 | a.chain(b) |
|
|||
74 | }; |
|
|||
75 |
|
||||
76 | let config_args = values_of_global_arg("config") |
|
|||
77 | .map(hg::utils::files::get_bytes_from_os_str); |
|
65 | .map(hg::utils::files::get_bytes_from_os_str); | |
78 | let non_repo_config = &hg::config::Config::load(config_args)?; |
|
66 | let non_repo_config = &hg::config::Config::load(config_args)?; | |
79 |
|
67 | |||
80 |
let repo_path = value_of_ |
|
68 | let repo_path = matches.value_of_os("repository").map(Path::new); | |
81 | let repo = match Repo::find(non_repo_config, repo_path) { |
|
69 | let repo = match Repo::find(non_repo_config, repo_path) { | |
82 | Ok(repo) => Ok(repo), |
|
70 | Ok(repo) => Ok(repo), | |
83 | Err(RepoError::NotFound { at }) if repo_path.is_none() => { |
|
71 | Err(RepoError::NotFound { at }) if repo_path.is_none() => { | |
84 | // Not finding a repo is not fatal yet, if `-R` was not given |
|
72 | // Not finding a repo is not fatal yet, if `-R` was not given | |
85 | Err(NoRepoInCwdError { cwd: at }) |
|
73 | Err(NoRepoInCwdError { cwd: at }) | |
86 | } |
|
74 | } | |
87 | Err(error) => return Err(error.into()), |
|
75 | Err(error) => return Err(error.into()), | |
88 | }; |
|
76 | }; | |
89 |
|
77 | |||
90 | let invocation = CliInvocation { |
|
78 | let invocation = CliInvocation { | |
91 | ui, |
|
79 | ui, | |
92 | subcommand_args, |
|
80 | subcommand_args, | |
93 | non_repo_config, |
|
81 | non_repo_config, | |
94 | repo: repo.as_ref(), |
|
82 | repo: repo.as_ref(), | |
95 | }; |
|
83 | }; | |
96 | let blackbox = blackbox::Blackbox::new(&invocation, process_start_time)?; |
|
84 | let blackbox = blackbox::Blackbox::new(&invocation, process_start_time)?; | |
97 | blackbox.log_command_start(); |
|
85 | blackbox.log_command_start(); | |
98 | let result = run(&invocation); |
|
86 | let result = run(&invocation); | |
99 | blackbox.log_command_end(exit_code(&result)); |
|
87 | blackbox.log_command_end(exit_code(&result)); | |
100 | result |
|
88 | result | |
101 | } |
|
89 | } | |
102 |
|
90 | |||
103 | fn main() { |
|
91 | fn main() { | |
104 | // Run this first, before we find out if the blackbox extension is even |
|
92 | // Run this first, before we find out if the blackbox extension is even | |
105 | // enabled, in order to include everything in-between in the duration |
|
93 | // enabled, in order to include everything in-between in the duration | |
106 | // measurements. Reading config files can be slow if they’re on NFS. |
|
94 | // measurements. Reading config files can be slow if they’re on NFS. | |
107 | let process_start_time = blackbox::ProcessStartTime::now(); |
|
95 | let process_start_time = blackbox::ProcessStartTime::now(); | |
108 |
|
96 | |||
109 | let ui = ui::Ui::new(); |
|
97 | let ui = ui::Ui::new(); | |
110 |
|
98 | |||
111 | let result = main_with_result(&ui, &process_start_time); |
|
99 | let result = main_with_result(&ui, &process_start_time); | |
112 | if let Err(CommandError::Abort { message }) = &result { |
|
100 | if let Err(CommandError::Abort { message }) = &result { | |
113 | if !message.is_empty() { |
|
101 | if !message.is_empty() { | |
114 | // Ignore errors when writing to stderr, we’re already exiting |
|
102 | // Ignore errors when writing to stderr, we’re already exiting | |
115 | // with failure code so there’s not much more we can do. |
|
103 | // with failure code so there’s not much more we can do. | |
116 | let _ = ui.write_stderr(&format_bytes!(b"abort: {}\n", message)); |
|
104 | let _ = ui.write_stderr(&format_bytes!(b"abort: {}\n", message)); | |
117 | } |
|
105 | } | |
118 | } |
|
106 | } | |
119 | std::process::exit(exit_code(&result)) |
|
107 | std::process::exit(exit_code(&result)) | |
120 | } |
|
108 | } | |
121 |
|
109 | |||
122 | fn exit_code(result: &Result<(), CommandError>) -> i32 { |
|
110 | fn exit_code(result: &Result<(), CommandError>) -> i32 { | |
123 | match result { |
|
111 | match result { | |
124 | Ok(()) => exitcode::OK, |
|
112 | Ok(()) => exitcode::OK, | |
125 | Err(CommandError::Abort { .. }) => exitcode::ABORT, |
|
113 | Err(CommandError::Abort { .. }) => exitcode::ABORT, | |
126 |
|
114 | |||
127 | // Exit with a specific code and no error message to let a potential |
|
115 | // Exit with a specific code and no error message to let a potential | |
128 | // wrapper script fallback to Python-based Mercurial. |
|
116 | // wrapper script fallback to Python-based Mercurial. | |
129 | Err(CommandError::Unimplemented) => exitcode::UNIMPLEMENTED, |
|
117 | Err(CommandError::Unimplemented) => exitcode::UNIMPLEMENTED, | |
130 | } |
|
118 | } | |
131 | } |
|
119 | } | |
132 |
|
120 | |||
133 | macro_rules! subcommands { |
|
121 | macro_rules! subcommands { | |
134 | ($( $command: ident )+) => { |
|
122 | ($( $command: ident )+) => { | |
135 | mod commands { |
|
123 | mod commands { | |
136 | $( |
|
124 | $( | |
137 | pub mod $command; |
|
125 | pub mod $command; | |
138 | )+ |
|
126 | )+ | |
139 | } |
|
127 | } | |
140 |
|
128 | |||
141 | fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { |
|
129 | fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { | |
142 | app |
|
130 | app | |
143 | $( |
|
131 | $( | |
144 |
.subcommand |
|
132 | .subcommand(commands::$command::args()) | |
145 | )+ |
|
133 | )+ | |
146 | } |
|
134 | } | |
147 |
|
135 | |||
148 | pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>; |
|
136 | pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>; | |
149 |
|
137 | |||
150 | fn subcommand_run_fn(name: &str) -> Option<RunFn> { |
|
138 | fn subcommand_run_fn(name: &str) -> Option<RunFn> { | |
151 | match name { |
|
139 | match name { | |
152 | $( |
|
140 | $( | |
153 | stringify!($command) => Some(commands::$command::run), |
|
141 | stringify!($command) => Some(commands::$command::run), | |
154 | )+ |
|
142 | )+ | |
155 | _ => None, |
|
143 | _ => None, | |
156 | } |
|
144 | } | |
157 | } |
|
145 | } | |
158 | }; |
|
146 | }; | |
159 | } |
|
147 | } | |
160 |
|
148 | |||
161 | subcommands! { |
|
149 | subcommands! { | |
162 | cat |
|
150 | cat | |
163 | debugdata |
|
151 | debugdata | |
164 | debugrequirements |
|
152 | debugrequirements | |
165 | files |
|
153 | files | |
166 | root |
|
154 | root | |
167 | config |
|
155 | config | |
168 | } |
|
156 | } | |
169 | pub struct CliInvocation<'a> { |
|
157 | pub struct CliInvocation<'a> { | |
170 | ui: &'a Ui, |
|
158 | ui: &'a Ui, | |
171 | subcommand_args: &'a ArgMatches<'a>, |
|
159 | subcommand_args: &'a ArgMatches<'a>, | |
172 | non_repo_config: &'a Config, |
|
160 | non_repo_config: &'a Config, | |
173 | /// References inside `Result` is a bit peculiar but allow |
|
161 | /// References inside `Result` is a bit peculiar but allow | |
174 | /// `invocation.repo?` to work out with `&CliInvocation` since this |
|
162 | /// `invocation.repo?` to work out with `&CliInvocation` since this | |
175 | /// `Result` type is `Copy`. |
|
163 | /// `Result` type is `Copy`. | |
176 | repo: Result<&'a Repo, &'a NoRepoInCwdError>, |
|
164 | repo: Result<&'a Repo, &'a NoRepoInCwdError>, | |
177 | } |
|
165 | } | |
178 |
|
166 | |||
179 | struct NoRepoInCwdError { |
|
167 | struct NoRepoInCwdError { | |
180 | cwd: PathBuf, |
|
168 | cwd: PathBuf, | |
181 | } |
|
169 | } | |
182 |
|
170 | |||
183 | impl CliInvocation<'_> { |
|
171 | impl CliInvocation<'_> { | |
184 | fn config(&self) -> &Config { |
|
172 | fn config(&self) -> &Config { | |
185 | if let Ok(repo) = self.repo { |
|
173 | if let Ok(repo) = self.repo { | |
186 | repo.config() |
|
174 | repo.config() | |
187 | } else { |
|
175 | } else { | |
188 | self.non_repo_config |
|
176 | self.non_repo_config | |
189 | } |
|
177 | } | |
190 | } |
|
178 | } | |
191 | } |
|
179 | } |
General Comments 0
You need to be logged in to leave comments.
Login now