##// END OF EJS Templates
rhg: Make configuration available as early as possible in main()...
Simon Sapin -
r47423:7284b524 default
parent child Browse files
Show More
@@ -52,20 +52,22 b" impl<'a> Blackbox<'a> {"
52 process_start_time: &'a ProcessStartTime,
52 process_start_time: &'a ProcessStartTime,
53 ) -> Result<Self, HgError> {
53 ) -> Result<Self, HgError> {
54 let configured = if let Ok(repo) = invocation.repo {
54 let configured = if let Ok(repo) = invocation.repo {
55 let config = invocation.config();
55 if invocation.config.get(b"extensions", b"blackbox").is_none() {
56 if config.get(b"extensions", b"blackbox").is_none() {
57 // The extension is not enabled
56 // The extension is not enabled
58 None
57 None
59 } else {
58 } else {
60 Some(ConfiguredBlackbox {
59 Some(ConfiguredBlackbox {
61 repo,
60 repo,
62 max_size: config
61 max_size: invocation
62 .config
63 .get_byte_size(b"blackbox", b"maxsize")?
63 .get_byte_size(b"blackbox", b"maxsize")?
64 .unwrap_or(DEFAULT_MAX_SIZE),
64 .unwrap_or(DEFAULT_MAX_SIZE),
65 max_files: config
65 max_files: invocation
66 .config
66 .get_u32(b"blackbox", b"maxfiles")?
67 .get_u32(b"blackbox", b"maxfiles")?
67 .unwrap_or(DEFAULT_MAX_FILES),
68 .unwrap_or(DEFAULT_MAX_FILES),
68 date_format: config
69 date_format: invocation
70 .config
69 .get_str(b"blackbox", b"date-format")?
71 .get_str(b"blackbox", b"date-format")?
70 .unwrap_or(DEFAULT_DATE_FORMAT),
72 .unwrap_or(DEFAULT_DATE_FORMAT),
71 })
73 })
@@ -29,7 +29,7 b' pub fn run(invocation: &crate::CliInvoca'
29 .split_2(b'.')
29 .split_2(b'.')
30 .ok_or_else(|| HgError::abort(""))?;
30 .ok_or_else(|| HgError::abort(""))?;
31
31
32 let value = invocation.config().get(section, name).unwrap_or(b"");
32 let value = invocation.config.get(section, name).unwrap_or(b"");
33
33
34 invocation.ui.write_stdout(&format_bytes!(b"{}\n", value))?;
34 invocation.ui.write_stdout(&format_bytes!(b"{}\n", value))?;
35 Ok(())
35 Ok(())
@@ -7,7 +7,10 b' 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 hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes};
11 use hg::utils::SliceExt;
12 use std::ffi::OsString;
13 use std::path::PathBuf;
11
14
12 mod blackbox;
15 mod blackbox;
13 mod error;
16 mod error;
@@ -16,10 +19,11 b' mod ui;'
16 use error::CommandError;
19 use error::CommandError;
17
20
18 fn main_with_result(
21 fn main_with_result(
22 process_start_time: &blackbox::ProcessStartTime,
19 ui: &ui::Ui,
23 ui: &ui::Ui,
20 process_start_time: &blackbox::ProcessStartTime,
24 repo: Result<&Repo, &NoRepoInCwdError>,
25 config: &Config,
21 ) -> Result<(), CommandError> {
26 ) -> Result<(), CommandError> {
22 env_logger::init();
23 let app = App::new("rhg")
27 let app = App::new("rhg")
24 .global_setting(AppSettings::AllowInvalidUtf8)
28 .global_setting(AppSettings::AllowInvalidUtf8)
25 .setting(AppSettings::SubcommandRequired)
29 .setting(AppSettings::SubcommandRequired)
@@ -57,29 +61,11 b' fn main_with_result('
57 let subcommand_args = subcommand_matches
61 let subcommand_args = subcommand_matches
58 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
62 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
59
63
60 let config_args = matches
61 .values_of_os("config")
62 // Turn `Option::None` into an empty iterator:
63 .into_iter()
64 .flatten()
65 .map(hg::utils::files::get_bytes_from_os_str);
66 let non_repo_config = &hg::config::Config::load(config_args)?;
67
68 let repo_path = matches.value_of_os("repository").map(Path::new);
69 let repo = match Repo::find(non_repo_config, repo_path) {
70 Ok(repo) => Ok(repo),
71 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
72 // Not finding a repo is not fatal yet, if `-R` was not given
73 Err(NoRepoInCwdError { cwd: at })
74 }
75 Err(error) => return Err(error.into()),
76 };
77
78 let invocation = CliInvocation {
64 let invocation = CliInvocation {
79 ui,
65 ui,
80 subcommand_args,
66 subcommand_args,
81 non_repo_config,
67 config,
82 repo: repo.as_ref(),
68 repo,
83 };
69 };
84 let blackbox = blackbox::Blackbox::new(&invocation, process_start_time)?;
70 let blackbox = blackbox::Blackbox::new(&invocation, process_start_time)?;
85 blackbox.log_command_start();
71 blackbox.log_command_start();
@@ -94,17 +80,36 b' fn main() {'
94 // measurements. Reading config files can be slow if they’re on NFS.
80 // measurements. Reading config files can be slow if they’re on NFS.
95 let process_start_time = blackbox::ProcessStartTime::now();
81 let process_start_time = blackbox::ProcessStartTime::now();
96
82
83 env_logger::init();
97 let ui = ui::Ui::new();
84 let ui = ui::Ui::new();
98
85
99 let result = main_with_result(&ui, &process_start_time);
86 let early_args = EarlyArgs::parse(std::env::args_os());
100 if let Err(CommandError::Abort { message }) = &result {
87 let non_repo_config = Config::load(early_args.config)
101 if !message.is_empty() {
88 .unwrap_or_else(|error| exit(&ui, Err(error.into())));
102 // Ignore errors when writing to stderr, we’re already exiting
89
103 // with failure code so there’s not much more we can do.
90 let repo_path = early_args.repo.as_deref().map(get_path_from_bytes);
104 let _ = ui.write_stderr(&format_bytes!(b"abort: {}\n", message));
91 let repo_result = match Repo::find(&non_repo_config, repo_path) {
92 Ok(repo) => Ok(repo),
93 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
94 // Not finding a repo is not fatal yet, if `-R` was not given
95 Err(NoRepoInCwdError { cwd: at })
105 }
96 }
106 }
97 Err(error) => exit(&ui, Err(error.into())),
107 std::process::exit(exit_code(&result))
98 };
99
100 let config = if let Ok(repo) = &repo_result {
101 repo.config()
102 } else {
103 &non_repo_config
104 };
105
106 let result = main_with_result(
107 &process_start_time,
108 &ui,
109 repo_result.as_ref(),
110 config,
111 );
112 exit(&ui, result)
108 }
113 }
109
114
110 fn exit_code(result: &Result<(), CommandError>) -> i32 {
115 fn exit_code(result: &Result<(), CommandError>) -> i32 {
@@ -118,6 +123,17 b' fn exit_code(result: &Result<(), Command'
118 }
123 }
119 }
124 }
120
125
126 fn exit(ui: &Ui, result: Result<(), CommandError>) -> ! {
127 if let Err(CommandError::Abort { message }) = &result {
128 if !message.is_empty() {
129 // Ignore errors when writing to stderr, we’re already exiting
130 // with failure code so there’s not much more we can do.
131 let _ = ui.write_stderr(&format_bytes!(b"abort: {}\n", message));
132 }
133 }
134 std::process::exit(exit_code(&result))
135 }
136
121 macro_rules! subcommands {
137 macro_rules! subcommands {
122 ($( $command: ident )+) => {
138 ($( $command: ident )+) => {
123 mod commands {
139 mod commands {
@@ -157,7 +173,7 b' subcommands! {'
157 pub struct CliInvocation<'a> {
173 pub struct CliInvocation<'a> {
158 ui: &'a Ui,
174 ui: &'a Ui,
159 subcommand_args: &'a ArgMatches<'a>,
175 subcommand_args: &'a ArgMatches<'a>,
160 non_repo_config: &'a Config,
176 config: &'a Config,
161 /// References inside `Result` is a bit peculiar but allow
177 /// References inside `Result` is a bit peculiar but allow
162 /// `invocation.repo?` to work out with `&CliInvocation` since this
178 /// `invocation.repo?` to work out with `&CliInvocation` since this
163 /// `Result` type is `Copy`.
179 /// `Result` type is `Copy`.
@@ -168,12 +184,45 b' struct NoRepoInCwdError {'
168 cwd: PathBuf,
184 cwd: PathBuf,
169 }
185 }
170
186
171 impl CliInvocation<'_> {
187 /// CLI arguments to be parsed "early" in order to be able to read
172 fn config(&self) -> &Config {
188 /// configuration before using Clap. Ideally we would also use Clap for this,
173 if let Ok(repo) = self.repo {
189 /// see <https://github.com/clap-rs/clap/discussions/2366>.
174 repo.config()
190 ///
175 } else {
191 /// These arguments are still declared when we do use Clap later, so that Clap
176 self.non_repo_config
192 /// does not return an error for their presence.
193 struct EarlyArgs {
194 /// Values of all `--config` arguments. (Possibly none)
195 config: Vec<Vec<u8>>,
196 /// Value of the `-R` or `--repository` argument, if any.
197 repo: Option<Vec<u8>>,
198 }
199
200 impl EarlyArgs {
201 fn parse(args: impl IntoIterator<Item = OsString>) -> Self {
202 let mut args = args.into_iter().map(get_bytes_from_os_str);
203 let mut config = Vec::new();
204 let mut repo = None;
205 // Use `while let` instead of `for` so that we can also call
206 // `args.next()` inside the loop.
207 while let Some(arg) = args.next() {
208 if arg == b"--config" {
209 if let Some(value) = args.next() {
210 config.push(value)
211 }
212 } else if let Some(value) = arg.drop_prefix(b"--config=") {
213 config.push(value.to_owned())
214 }
215
216 if arg == b"--repository" || arg == b"-R" {
217 if let Some(value) = args.next() {
218 repo = Some(value)
219 }
220 } else if let Some(value) = arg.drop_prefix(b"--repository=") {
221 repo = Some(value.to_owned())
222 } else if let Some(value) = arg.drop_prefix(b"-R") {
223 repo = Some(value.to_owned())
224 }
177 }
225 }
226 Self { config, repo }
178 }
227 }
179 }
228 }
General Comments 0
You need to be logged in to leave comments. Login now