##// 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
@@ -1,161 +1,163 b''
1 //! Logging for repository events, including commands run in the repository.
1 //! Logging for repository events, including commands run in the repository.
2
2
3 use crate::CliInvocation;
3 use crate::CliInvocation;
4 use format_bytes::format_bytes;
4 use format_bytes::format_bytes;
5 use hg::errors::HgError;
5 use hg::errors::HgError;
6 use hg::repo::Repo;
6 use hg::repo::Repo;
7 use hg::utils::{files::get_bytes_from_os_str, shell_quote};
7 use hg::utils::{files::get_bytes_from_os_str, shell_quote};
8
8
9 const ONE_MEBIBYTE: u64 = 1 << 20;
9 const ONE_MEBIBYTE: u64 = 1 << 20;
10
10
11 // TODO: somehow keep defaults in sync with `configitem` in `hgext/blackbox.py`
11 // TODO: somehow keep defaults in sync with `configitem` in `hgext/blackbox.py`
12 const DEFAULT_MAX_SIZE: u64 = ONE_MEBIBYTE;
12 const DEFAULT_MAX_SIZE: u64 = ONE_MEBIBYTE;
13 const DEFAULT_MAX_FILES: u32 = 7;
13 const DEFAULT_MAX_FILES: u32 = 7;
14
14
15 // Python does not support %.3f, only %f
15 // Python does not support %.3f, only %f
16 const DEFAULT_DATE_FORMAT: &str = "%Y/%m/%d %H:%M:%S%.3f";
16 const DEFAULT_DATE_FORMAT: &str = "%Y/%m/%d %H:%M:%S%.3f";
17
17
18 type DateTime = chrono::DateTime<chrono::Local>;
18 type DateTime = chrono::DateTime<chrono::Local>;
19
19
20 pub struct ProcessStartTime {
20 pub struct ProcessStartTime {
21 /// For measuring duration
21 /// For measuring duration
22 monotonic_clock: std::time::Instant,
22 monotonic_clock: std::time::Instant,
23 /// For formatting with year, month, day, etc.
23 /// For formatting with year, month, day, etc.
24 calendar_based: DateTime,
24 calendar_based: DateTime,
25 }
25 }
26
26
27 impl ProcessStartTime {
27 impl ProcessStartTime {
28 pub fn now() -> Self {
28 pub fn now() -> Self {
29 Self {
29 Self {
30 monotonic_clock: std::time::Instant::now(),
30 monotonic_clock: std::time::Instant::now(),
31 calendar_based: chrono::Local::now(),
31 calendar_based: chrono::Local::now(),
32 }
32 }
33 }
33 }
34 }
34 }
35
35
36 pub struct Blackbox<'a> {
36 pub struct Blackbox<'a> {
37 process_start_time: &'a ProcessStartTime,
37 process_start_time: &'a ProcessStartTime,
38 /// Do nothing if this is `None`
38 /// Do nothing if this is `None`
39 configured: Option<ConfiguredBlackbox<'a>>,
39 configured: Option<ConfiguredBlackbox<'a>>,
40 }
40 }
41
41
42 struct ConfiguredBlackbox<'a> {
42 struct ConfiguredBlackbox<'a> {
43 repo: &'a Repo,
43 repo: &'a Repo,
44 max_size: u64,
44 max_size: u64,
45 max_files: u32,
45 max_files: u32,
46 date_format: &'a str,
46 date_format: &'a str,
47 }
47 }
48
48
49 impl<'a> Blackbox<'a> {
49 impl<'a> Blackbox<'a> {
50 pub fn new(
50 pub fn new(
51 invocation: &'a CliInvocation<'a>,
51 invocation: &'a CliInvocation<'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 })
72 }
74 }
73 } else {
75 } else {
74 // Without a local repository there’s no `.hg/blackbox.log` to
76 // Without a local repository there’s no `.hg/blackbox.log` to
75 // write to.
77 // write to.
76 None
78 None
77 };
79 };
78 Ok(Self {
80 Ok(Self {
79 process_start_time,
81 process_start_time,
80 configured,
82 configured,
81 })
83 })
82 }
84 }
83
85
84 pub fn log_command_start(&self) {
86 pub fn log_command_start(&self) {
85 if let Some(configured) = &self.configured {
87 if let Some(configured) = &self.configured {
86 let message = format_bytes!(b"(rust) {}", format_cli_args());
88 let message = format_bytes!(b"(rust) {}", format_cli_args());
87 configured.log(&self.process_start_time.calendar_based, &message);
89 configured.log(&self.process_start_time.calendar_based, &message);
88 }
90 }
89 }
91 }
90
92
91 pub fn log_command_end(&self, exit_code: i32) {
93 pub fn log_command_end(&self, exit_code: i32) {
92 if let Some(configured) = &self.configured {
94 if let Some(configured) = &self.configured {
93 let now = chrono::Local::now();
95 let now = chrono::Local::now();
94 let duration = self
96 let duration = self
95 .process_start_time
97 .process_start_time
96 .monotonic_clock
98 .monotonic_clock
97 .elapsed()
99 .elapsed()
98 .as_secs_f64();
100 .as_secs_f64();
99 let message = format_bytes!(
101 let message = format_bytes!(
100 b"(rust) {} exited {} after {} seconds",
102 b"(rust) {} exited {} after {} seconds",
101 format_cli_args(),
103 format_cli_args(),
102 exit_code,
104 exit_code,
103 format_bytes::Utf8(format_args!("{:.03}", duration))
105 format_bytes::Utf8(format_args!("{:.03}", duration))
104 );
106 );
105 configured.log(&now, &message);
107 configured.log(&now, &message);
106 }
108 }
107 }
109 }
108 }
110 }
109
111
110 impl ConfiguredBlackbox<'_> {
112 impl ConfiguredBlackbox<'_> {
111 fn log(&self, date_time: &DateTime, message: &[u8]) {
113 fn log(&self, date_time: &DateTime, message: &[u8]) {
112 let date = format_bytes::Utf8(date_time.format(self.date_format));
114 let date = format_bytes::Utf8(date_time.format(self.date_format));
113 let user = users::get_current_username().map(get_bytes_from_os_str);
115 let user = users::get_current_username().map(get_bytes_from_os_str);
114 let user = user.as_deref().unwrap_or(b"???");
116 let user = user.as_deref().unwrap_or(b"???");
115 let rev = format_bytes::Utf8(match self.repo.dirstate_parents() {
117 let rev = format_bytes::Utf8(match self.repo.dirstate_parents() {
116 Ok(parents) if parents.p2 == hg::revlog::node::NULL_NODE => {
118 Ok(parents) if parents.p2 == hg::revlog::node::NULL_NODE => {
117 format!("{:x}", parents.p1)
119 format!("{:x}", parents.p1)
118 }
120 }
119 Ok(parents) => format!("{:x}+{:x}", parents.p1, parents.p2),
121 Ok(parents) => format!("{:x}+{:x}", parents.p1, parents.p2),
120 Err(_dirstate_corruption_error) => {
122 Err(_dirstate_corruption_error) => {
121 // TODO: log a non-fatal warning to stderr
123 // TODO: log a non-fatal warning to stderr
122 "???".to_owned()
124 "???".to_owned()
123 }
125 }
124 });
126 });
125 let pid = std::process::id();
127 let pid = std::process::id();
126 let line = format_bytes!(
128 let line = format_bytes!(
127 b"{} {} @{} ({})> {}\n",
129 b"{} {} @{} ({})> {}\n",
128 date,
130 date,
129 user,
131 user,
130 rev,
132 rev,
131 pid,
133 pid,
132 message
134 message
133 );
135 );
134 let result =
136 let result =
135 hg::logging::LogFile::new(self.repo.hg_vfs(), "blackbox.log")
137 hg::logging::LogFile::new(self.repo.hg_vfs(), "blackbox.log")
136 .max_size(Some(self.max_size))
138 .max_size(Some(self.max_size))
137 .max_files(self.max_files)
139 .max_files(self.max_files)
138 .write(&line);
140 .write(&line);
139 match result {
141 match result {
140 Ok(()) => {}
142 Ok(()) => {}
141 Err(_io_error) => {
143 Err(_io_error) => {
142 // TODO: log a non-fatal warning to stderr
144 // TODO: log a non-fatal warning to stderr
143 }
145 }
144 }
146 }
145 }
147 }
146 }
148 }
147
149
148 fn format_cli_args() -> Vec<u8> {
150 fn format_cli_args() -> Vec<u8> {
149 let mut args = std::env::args_os();
151 let mut args = std::env::args_os();
150 let _ = args.next(); // Skip the first (or zeroth) arg, the name of the `rhg` executable
152 let _ = args.next(); // Skip the first (or zeroth) arg, the name of the `rhg` executable
151 let mut args = args.map(|arg| shell_quote(&get_bytes_from_os_str(arg)));
153 let mut args = args.map(|arg| shell_quote(&get_bytes_from_os_str(arg)));
152 let mut formatted = Vec::new();
154 let mut formatted = Vec::new();
153 if let Some(arg) = args.next() {
155 if let Some(arg) = args.next() {
154 formatted.extend(arg)
156 formatted.extend(arg)
155 }
157 }
156 for arg in args {
158 for arg in args {
157 formatted.push(b' ');
159 formatted.push(b' ');
158 formatted.extend(arg)
160 formatted.extend(arg)
159 }
161 }
160 formatted
162 formatted
161 }
163 }
@@ -1,36 +1,36 b''
1 use crate::error::CommandError;
1 use crate::error::CommandError;
2 use clap::Arg;
2 use clap::Arg;
3 use format_bytes::format_bytes;
3 use format_bytes::format_bytes;
4 use hg::errors::HgError;
4 use hg::errors::HgError;
5 use hg::utils::SliceExt;
5 use hg::utils::SliceExt;
6
6
7 pub const HELP_TEXT: &str = "
7 pub const HELP_TEXT: &str = "
8 With one argument of the form section.name, print just the value of that config item.
8 With one argument of the form section.name, print just the value of that config item.
9 ";
9 ";
10
10
11 pub fn args() -> clap::App<'static, 'static> {
11 pub fn args() -> clap::App<'static, 'static> {
12 clap::SubCommand::with_name("config")
12 clap::SubCommand::with_name("config")
13 .arg(
13 .arg(
14 Arg::with_name("name")
14 Arg::with_name("name")
15 .help("the section.name to print")
15 .help("the section.name to print")
16 .value_name("NAME")
16 .value_name("NAME")
17 .required(true)
17 .required(true)
18 .takes_value(true),
18 .takes_value(true),
19 )
19 )
20 .about(HELP_TEXT)
20 .about(HELP_TEXT)
21 }
21 }
22
22
23 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
23 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
24 let (section, name) = invocation
24 let (section, name) = invocation
25 .subcommand_args
25 .subcommand_args
26 .value_of("name")
26 .value_of("name")
27 .expect("missing required CLI argument")
27 .expect("missing required CLI argument")
28 .as_bytes()
28 .as_bytes()
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(())
36 }
36 }
@@ -1,179 +1,228 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 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;
14 mod exitcode;
17 mod exitcode;
15 mod ui;
18 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)
26 .setting(AppSettings::VersionlessSubcommands)
30 .setting(AppSettings::VersionlessSubcommands)
27 .arg(
31 .arg(
28 Arg::with_name("repository")
32 Arg::with_name("repository")
29 .help("repository root directory")
33 .help("repository root directory")
30 .short("-R")
34 .short("-R")
31 .long("--repository")
35 .long("--repository")
32 .value_name("REPO")
36 .value_name("REPO")
33 .takes_value(true)
37 .takes_value(true)
34 // Both ok: `hg -R ./foo log` or `hg log -R ./foo`
38 // Both ok: `hg -R ./foo log` or `hg log -R ./foo`
35 .global(true),
39 .global(true),
36 )
40 )
37 .arg(
41 .arg(
38 Arg::with_name("config")
42 Arg::with_name("config")
39 .help("set/override config option (use 'section.name=value')")
43 .help("set/override config option (use 'section.name=value')")
40 .long("--config")
44 .long("--config")
41 .value_name("CONFIG")
45 .value_name("CONFIG")
42 .takes_value(true)
46 .takes_value(true)
43 .global(true)
47 .global(true)
44 // Ok: `--config section.key1=val --config section.key2=val2`
48 // Ok: `--config section.key1=val --config section.key2=val2`
45 .multiple(true)
49 .multiple(true)
46 // Not ok: `--config section.key1=val section.key2=val2`
50 // Not ok: `--config section.key1=val section.key2=val2`
47 .number_of_values(1),
51 .number_of_values(1),
48 )
52 )
49 .version("0.0.1");
53 .version("0.0.1");
50 let app = add_subcommand_args(app);
54 let app = add_subcommand_args(app);
51
55
52 let matches = app.clone().get_matches_safe()?;
56 let matches = app.clone().get_matches_safe()?;
53
57
54 let (subcommand_name, subcommand_matches) = matches.subcommand();
58 let (subcommand_name, subcommand_matches) = matches.subcommand();
55 let run = subcommand_run_fn(subcommand_name)
59 let run = subcommand_run_fn(subcommand_name)
56 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
60 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
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();
86 let result = run(&invocation);
72 let result = run(&invocation);
87 blackbox.log_command_end(exit_code(&result));
73 blackbox.log_command_end(exit_code(&result));
88 result
74 result
89 }
75 }
90
76
91 fn main() {
77 fn main() {
92 // Run this first, before we find out if the blackbox extension is even
78 // Run this first, before we find out if the blackbox extension is even
93 // enabled, in order to include everything in-between in the duration
79 // enabled, in order to include everything in-between in the duration
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 {
111 match result {
116 match result {
112 Ok(()) => exitcode::OK,
117 Ok(()) => exitcode::OK,
113 Err(CommandError::Abort { .. }) => exitcode::ABORT,
118 Err(CommandError::Abort { .. }) => exitcode::ABORT,
114
119
115 // Exit with a specific code and no error message to let a potential
120 // Exit with a specific code and no error message to let a potential
116 // wrapper script fallback to Python-based Mercurial.
121 // wrapper script fallback to Python-based Mercurial.
117 Err(CommandError::Unimplemented) => exitcode::UNIMPLEMENTED,
122 Err(CommandError::Unimplemented) => exitcode::UNIMPLEMENTED,
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 {
124 $(
140 $(
125 pub mod $command;
141 pub mod $command;
126 )+
142 )+
127 }
143 }
128
144
129 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
145 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
130 app
146 app
131 $(
147 $(
132 .subcommand(commands::$command::args())
148 .subcommand(commands::$command::args())
133 )+
149 )+
134 }
150 }
135
151
136 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
152 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
137
153
138 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
154 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
139 match name {
155 match name {
140 $(
156 $(
141 stringify!($command) => Some(commands::$command::run),
157 stringify!($command) => Some(commands::$command::run),
142 )+
158 )+
143 _ => None,
159 _ => None,
144 }
160 }
145 }
161 }
146 };
162 };
147 }
163 }
148
164
149 subcommands! {
165 subcommands! {
150 cat
166 cat
151 debugdata
167 debugdata
152 debugrequirements
168 debugrequirements
153 files
169 files
154 root
170 root
155 config
171 config
156 }
172 }
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`.
164 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
180 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
165 }
181 }
166
182
167 struct NoRepoInCwdError {
183 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