##// END OF EJS Templates
rhg: Pass a &Config to Ui::new...
Simon Sapin -
r49581:1aaf11e3 default
parent child Browse files
Show More
@@ -1,539 +1,544 b''
1 // config.rs
1 // config.rs
2 //
2 //
3 // Copyright 2020
3 // Copyright 2020
4 // Valentin Gatien-Baron,
4 // Valentin Gatien-Baron,
5 // Raphaël Gomès <rgomes@octobus.net>
5 // Raphaël Gomès <rgomes@octobus.net>
6 //
6 //
7 // This software may be used and distributed according to the terms of the
7 // This software may be used and distributed according to the terms of the
8 // GNU General Public License version 2 or any later version.
8 // GNU General Public License version 2 or any later version.
9
9
10 use super::layer;
10 use super::layer;
11 use super::values;
11 use super::values;
12 use crate::config::layer::{
12 use crate::config::layer::{
13 ConfigError, ConfigLayer, ConfigOrigin, ConfigValue,
13 ConfigError, ConfigLayer, ConfigOrigin, ConfigValue,
14 };
14 };
15 use crate::utils::files::get_bytes_from_os_str;
15 use crate::utils::files::get_bytes_from_os_str;
16 use format_bytes::{write_bytes, DisplayBytes};
16 use format_bytes::{write_bytes, DisplayBytes};
17 use std::collections::HashSet;
17 use std::collections::HashSet;
18 use std::env;
18 use std::env;
19 use std::fmt;
19 use std::fmt;
20 use std::path::{Path, PathBuf};
20 use std::path::{Path, PathBuf};
21 use std::str;
21 use std::str;
22
22
23 use crate::errors::{HgResultExt, IoResultExt};
23 use crate::errors::{HgResultExt, IoResultExt};
24
24
25 /// Holds the config values for the current repository
25 /// Holds the config values for the current repository
26 /// TODO update this docstring once we support more sources
26 /// TODO update this docstring once we support more sources
27 #[derive(Clone)]
27 #[derive(Clone)]
28 pub struct Config {
28 pub struct Config {
29 layers: Vec<layer::ConfigLayer>,
29 layers: Vec<layer::ConfigLayer>,
30 }
30 }
31
31
32 impl DisplayBytes for Config {
32 impl DisplayBytes for Config {
33 fn display_bytes(
33 fn display_bytes(
34 &self,
34 &self,
35 out: &mut dyn std::io::Write,
35 out: &mut dyn std::io::Write,
36 ) -> std::io::Result<()> {
36 ) -> std::io::Result<()> {
37 for (index, layer) in self.layers.iter().rev().enumerate() {
37 for (index, layer) in self.layers.iter().rev().enumerate() {
38 write_bytes!(
38 write_bytes!(
39 out,
39 out,
40 b"==== Layer {} (trusted: {}) ====\n{}",
40 b"==== Layer {} (trusted: {}) ====\n{}",
41 index,
41 index,
42 if layer.trusted {
42 if layer.trusted {
43 &b"yes"[..]
43 &b"yes"[..]
44 } else {
44 } else {
45 &b"no"[..]
45 &b"no"[..]
46 },
46 },
47 layer
47 layer
48 )?;
48 )?;
49 }
49 }
50 Ok(())
50 Ok(())
51 }
51 }
52 }
52 }
53
53
54 pub enum ConfigSource {
54 pub enum ConfigSource {
55 /// Absolute path to a config file
55 /// Absolute path to a config file
56 AbsPath(PathBuf),
56 AbsPath(PathBuf),
57 /// Already parsed (from the CLI, env, Python resources, etc.)
57 /// Already parsed (from the CLI, env, Python resources, etc.)
58 Parsed(layer::ConfigLayer),
58 Parsed(layer::ConfigLayer),
59 }
59 }
60
60
61 #[derive(Debug)]
61 #[derive(Debug)]
62 pub struct ConfigValueParseError {
62 pub struct ConfigValueParseError {
63 pub origin: ConfigOrigin,
63 pub origin: ConfigOrigin,
64 pub line: Option<usize>,
64 pub line: Option<usize>,
65 pub section: Vec<u8>,
65 pub section: Vec<u8>,
66 pub item: Vec<u8>,
66 pub item: Vec<u8>,
67 pub value: Vec<u8>,
67 pub value: Vec<u8>,
68 pub expected_type: &'static str,
68 pub expected_type: &'static str,
69 }
69 }
70
70
71 impl fmt::Display for ConfigValueParseError {
71 impl fmt::Display for ConfigValueParseError {
72 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
72 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
73 // TODO: add origin and line number information, here and in
73 // TODO: add origin and line number information, here and in
74 // corresponding python code
74 // corresponding python code
75 write!(
75 write!(
76 f,
76 f,
77 "config error: {}.{} is not a {} ('{}')",
77 "config error: {}.{} is not a {} ('{}')",
78 String::from_utf8_lossy(&self.section),
78 String::from_utf8_lossy(&self.section),
79 String::from_utf8_lossy(&self.item),
79 String::from_utf8_lossy(&self.item),
80 self.expected_type,
80 self.expected_type,
81 String::from_utf8_lossy(&self.value)
81 String::from_utf8_lossy(&self.value)
82 )
82 )
83 }
83 }
84 }
84 }
85
85
86 impl Config {
86 impl Config {
87 /// The configuration to use when printing configuration-loading errors
88 pub fn empty() -> Self {
89 Self { layers: Vec::new() }
90 }
91
87 /// Load system and user configuration from various files.
92 /// Load system and user configuration from various files.
88 ///
93 ///
89 /// This is also affected by some environment variables.
94 /// This is also affected by some environment variables.
90 pub fn load_non_repo() -> Result<Self, ConfigError> {
95 pub fn load_non_repo() -> Result<Self, ConfigError> {
91 let mut config = Self { layers: Vec::new() };
96 let mut config = Self { layers: Vec::new() };
92 let opt_rc_path = env::var_os("HGRCPATH");
97 let opt_rc_path = env::var_os("HGRCPATH");
93 // HGRCPATH replaces system config
98 // HGRCPATH replaces system config
94 if opt_rc_path.is_none() {
99 if opt_rc_path.is_none() {
95 config.add_system_config()?
100 config.add_system_config()?
96 }
101 }
97
102
98 config.add_for_environment_variable("EDITOR", b"ui", b"editor");
103 config.add_for_environment_variable("EDITOR", b"ui", b"editor");
99 config.add_for_environment_variable("VISUAL", b"ui", b"editor");
104 config.add_for_environment_variable("VISUAL", b"ui", b"editor");
100 config.add_for_environment_variable("PAGER", b"pager", b"pager");
105 config.add_for_environment_variable("PAGER", b"pager", b"pager");
101
106
102 // These are set by `run-tests.py --rhg` to enable fallback for the
107 // These are set by `run-tests.py --rhg` to enable fallback for the
103 // entire test suite. Alternatives would be setting configuration
108 // entire test suite. Alternatives would be setting configuration
104 // through `$HGRCPATH` but some tests override that, or changing the
109 // through `$HGRCPATH` but some tests override that, or changing the
105 // `hg` shell alias to include `--config` but that disrupts tests that
110 // `hg` shell alias to include `--config` but that disrupts tests that
106 // print command lines and check expected output.
111 // print command lines and check expected output.
107 config.add_for_environment_variable(
112 config.add_for_environment_variable(
108 "RHG_ON_UNSUPPORTED",
113 "RHG_ON_UNSUPPORTED",
109 b"rhg",
114 b"rhg",
110 b"on-unsupported",
115 b"on-unsupported",
111 );
116 );
112 config.add_for_environment_variable(
117 config.add_for_environment_variable(
113 "RHG_FALLBACK_EXECUTABLE",
118 "RHG_FALLBACK_EXECUTABLE",
114 b"rhg",
119 b"rhg",
115 b"fallback-executable",
120 b"fallback-executable",
116 );
121 );
117 config.add_for_environment_variable("RHG_STATUS", b"rhg", b"status");
122 config.add_for_environment_variable("RHG_STATUS", b"rhg", b"status");
118
123
119 // HGRCPATH replaces user config
124 // HGRCPATH replaces user config
120 if opt_rc_path.is_none() {
125 if opt_rc_path.is_none() {
121 config.add_user_config()?
126 config.add_user_config()?
122 }
127 }
123 if let Some(rc_path) = &opt_rc_path {
128 if let Some(rc_path) = &opt_rc_path {
124 for path in env::split_paths(rc_path) {
129 for path in env::split_paths(rc_path) {
125 if !path.as_os_str().is_empty() {
130 if !path.as_os_str().is_empty() {
126 if path.is_dir() {
131 if path.is_dir() {
127 config.add_trusted_dir(&path)?
132 config.add_trusted_dir(&path)?
128 } else {
133 } else {
129 config.add_trusted_file(&path)?
134 config.add_trusted_file(&path)?
130 }
135 }
131 }
136 }
132 }
137 }
133 }
138 }
134 Ok(config)
139 Ok(config)
135 }
140 }
136
141
137 pub fn load_cli_args_config(
142 pub fn load_cli_args_config(
138 &mut self,
143 &mut self,
139 cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>,
144 cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>,
140 ) -> Result<(), ConfigError> {
145 ) -> Result<(), ConfigError> {
141 if let Some(layer) = ConfigLayer::parse_cli_args(cli_config_args)? {
146 if let Some(layer) = ConfigLayer::parse_cli_args(cli_config_args)? {
142 self.layers.push(layer)
147 self.layers.push(layer)
143 }
148 }
144 Ok(())
149 Ok(())
145 }
150 }
146
151
147 fn add_trusted_dir(&mut self, path: &Path) -> Result<(), ConfigError> {
152 fn add_trusted_dir(&mut self, path: &Path) -> Result<(), ConfigError> {
148 if let Some(entries) = std::fs::read_dir(path)
153 if let Some(entries) = std::fs::read_dir(path)
149 .when_reading_file(path)
154 .when_reading_file(path)
150 .io_not_found_as_none()?
155 .io_not_found_as_none()?
151 {
156 {
152 let mut file_paths = entries
157 let mut file_paths = entries
153 .map(|result| {
158 .map(|result| {
154 result.when_reading_file(path).map(|entry| entry.path())
159 result.when_reading_file(path).map(|entry| entry.path())
155 })
160 })
156 .collect::<Result<Vec<_>, _>>()?;
161 .collect::<Result<Vec<_>, _>>()?;
157 file_paths.sort();
162 file_paths.sort();
158 for file_path in &file_paths {
163 for file_path in &file_paths {
159 if file_path.extension() == Some(std::ffi::OsStr::new("rc")) {
164 if file_path.extension() == Some(std::ffi::OsStr::new("rc")) {
160 self.add_trusted_file(&file_path)?
165 self.add_trusted_file(&file_path)?
161 }
166 }
162 }
167 }
163 }
168 }
164 Ok(())
169 Ok(())
165 }
170 }
166
171
167 fn add_trusted_file(&mut self, path: &Path) -> Result<(), ConfigError> {
172 fn add_trusted_file(&mut self, path: &Path) -> Result<(), ConfigError> {
168 if let Some(data) = std::fs::read(path)
173 if let Some(data) = std::fs::read(path)
169 .when_reading_file(path)
174 .when_reading_file(path)
170 .io_not_found_as_none()?
175 .io_not_found_as_none()?
171 {
176 {
172 self.layers.extend(ConfigLayer::parse(path, &data)?)
177 self.layers.extend(ConfigLayer::parse(path, &data)?)
173 }
178 }
174 Ok(())
179 Ok(())
175 }
180 }
176
181
177 fn add_for_environment_variable(
182 fn add_for_environment_variable(
178 &mut self,
183 &mut self,
179 var: &str,
184 var: &str,
180 section: &[u8],
185 section: &[u8],
181 key: &[u8],
186 key: &[u8],
182 ) {
187 ) {
183 if let Some(value) = env::var_os(var) {
188 if let Some(value) = env::var_os(var) {
184 let origin = layer::ConfigOrigin::Environment(var.into());
189 let origin = layer::ConfigOrigin::Environment(var.into());
185 let mut layer = ConfigLayer::new(origin);
190 let mut layer = ConfigLayer::new(origin);
186 layer.add(
191 layer.add(
187 section.to_owned(),
192 section.to_owned(),
188 key.to_owned(),
193 key.to_owned(),
189 get_bytes_from_os_str(value),
194 get_bytes_from_os_str(value),
190 None,
195 None,
191 );
196 );
192 self.layers.push(layer)
197 self.layers.push(layer)
193 }
198 }
194 }
199 }
195
200
196 #[cfg(unix)] // TODO: other platforms
201 #[cfg(unix)] // TODO: other platforms
197 fn add_system_config(&mut self) -> Result<(), ConfigError> {
202 fn add_system_config(&mut self) -> Result<(), ConfigError> {
198 let mut add_for_prefix = |prefix: &Path| -> Result<(), ConfigError> {
203 let mut add_for_prefix = |prefix: &Path| -> Result<(), ConfigError> {
199 let etc = prefix.join("etc").join("mercurial");
204 let etc = prefix.join("etc").join("mercurial");
200 self.add_trusted_file(&etc.join("hgrc"))?;
205 self.add_trusted_file(&etc.join("hgrc"))?;
201 self.add_trusted_dir(&etc.join("hgrc.d"))
206 self.add_trusted_dir(&etc.join("hgrc.d"))
202 };
207 };
203 let root = Path::new("/");
208 let root = Path::new("/");
204 // TODO: use `std::env::args_os().next().unwrap()` a.k.a. argv[0]
209 // TODO: use `std::env::args_os().next().unwrap()` a.k.a. argv[0]
205 // instead? TODO: can this be a relative path?
210 // instead? TODO: can this be a relative path?
206 let hg = crate::utils::current_exe()?;
211 let hg = crate::utils::current_exe()?;
207 // TODO: this order (per-installation then per-system) matches
212 // TODO: this order (per-installation then per-system) matches
208 // `systemrcpath()` in `mercurial/scmposix.py`, but
213 // `systemrcpath()` in `mercurial/scmposix.py`, but
209 // `mercurial/helptext/config.txt` suggests it should be reversed
214 // `mercurial/helptext/config.txt` suggests it should be reversed
210 if let Some(installation_prefix) = hg.parent().and_then(Path::parent) {
215 if let Some(installation_prefix) = hg.parent().and_then(Path::parent) {
211 if installation_prefix != root {
216 if installation_prefix != root {
212 add_for_prefix(&installation_prefix)?
217 add_for_prefix(&installation_prefix)?
213 }
218 }
214 }
219 }
215 add_for_prefix(root)?;
220 add_for_prefix(root)?;
216 Ok(())
221 Ok(())
217 }
222 }
218
223
219 #[cfg(unix)] // TODO: other plateforms
224 #[cfg(unix)] // TODO: other plateforms
220 fn add_user_config(&mut self) -> Result<(), ConfigError> {
225 fn add_user_config(&mut self) -> Result<(), ConfigError> {
221 let opt_home = home::home_dir();
226 let opt_home = home::home_dir();
222 if let Some(home) = &opt_home {
227 if let Some(home) = &opt_home {
223 self.add_trusted_file(&home.join(".hgrc"))?
228 self.add_trusted_file(&home.join(".hgrc"))?
224 }
229 }
225 let darwin = cfg!(any(target_os = "macos", target_os = "ios"));
230 let darwin = cfg!(any(target_os = "macos", target_os = "ios"));
226 if !darwin {
231 if !darwin {
227 if let Some(config_home) = env::var_os("XDG_CONFIG_HOME")
232 if let Some(config_home) = env::var_os("XDG_CONFIG_HOME")
228 .map(PathBuf::from)
233 .map(PathBuf::from)
229 .or_else(|| opt_home.map(|home| home.join(".config")))
234 .or_else(|| opt_home.map(|home| home.join(".config")))
230 {
235 {
231 self.add_trusted_file(&config_home.join("hg").join("hgrc"))?
236 self.add_trusted_file(&config_home.join("hg").join("hgrc"))?
232 }
237 }
233 }
238 }
234 Ok(())
239 Ok(())
235 }
240 }
236
241
237 /// Loads in order, which means that the precedence is the same
242 /// Loads in order, which means that the precedence is the same
238 /// as the order of `sources`.
243 /// as the order of `sources`.
239 pub fn load_from_explicit_sources(
244 pub fn load_from_explicit_sources(
240 sources: Vec<ConfigSource>,
245 sources: Vec<ConfigSource>,
241 ) -> Result<Self, ConfigError> {
246 ) -> Result<Self, ConfigError> {
242 let mut layers = vec![];
247 let mut layers = vec![];
243
248
244 for source in sources.into_iter() {
249 for source in sources.into_iter() {
245 match source {
250 match source {
246 ConfigSource::Parsed(c) => layers.push(c),
251 ConfigSource::Parsed(c) => layers.push(c),
247 ConfigSource::AbsPath(c) => {
252 ConfigSource::AbsPath(c) => {
248 // TODO check if it should be trusted
253 // TODO check if it should be trusted
249 // mercurial/ui.py:427
254 // mercurial/ui.py:427
250 let data = match std::fs::read(&c) {
255 let data = match std::fs::read(&c) {
251 Err(_) => continue, // same as the python code
256 Err(_) => continue, // same as the python code
252 Ok(data) => data,
257 Ok(data) => data,
253 };
258 };
254 layers.extend(ConfigLayer::parse(&c, &data)?)
259 layers.extend(ConfigLayer::parse(&c, &data)?)
255 }
260 }
256 }
261 }
257 }
262 }
258
263
259 Ok(Config { layers })
264 Ok(Config { layers })
260 }
265 }
261
266
262 /// Loads the per-repository config into a new `Config` which is combined
267 /// Loads the per-repository config into a new `Config` which is combined
263 /// with `self`.
268 /// with `self`.
264 pub(crate) fn combine_with_repo(
269 pub(crate) fn combine_with_repo(
265 &self,
270 &self,
266 repo_config_files: &[PathBuf],
271 repo_config_files: &[PathBuf],
267 ) -> Result<Self, ConfigError> {
272 ) -> Result<Self, ConfigError> {
268 let (cli_layers, other_layers) = self
273 let (cli_layers, other_layers) = self
269 .layers
274 .layers
270 .iter()
275 .iter()
271 .cloned()
276 .cloned()
272 .partition(ConfigLayer::is_from_command_line);
277 .partition(ConfigLayer::is_from_command_line);
273
278
274 let mut repo_config = Self {
279 let mut repo_config = Self {
275 layers: other_layers,
280 layers: other_layers,
276 };
281 };
277 for path in repo_config_files {
282 for path in repo_config_files {
278 // TODO: check if this file should be trusted:
283 // TODO: check if this file should be trusted:
279 // `mercurial/ui.py:427`
284 // `mercurial/ui.py:427`
280 repo_config.add_trusted_file(path)?;
285 repo_config.add_trusted_file(path)?;
281 }
286 }
282 repo_config.layers.extend(cli_layers);
287 repo_config.layers.extend(cli_layers);
283 Ok(repo_config)
288 Ok(repo_config)
284 }
289 }
285
290
286 fn get_parse<'config, T: 'config>(
291 fn get_parse<'config, T: 'config>(
287 &'config self,
292 &'config self,
288 section: &[u8],
293 section: &[u8],
289 item: &[u8],
294 item: &[u8],
290 expected_type: &'static str,
295 expected_type: &'static str,
291 parse: impl Fn(&'config [u8]) -> Option<T>,
296 parse: impl Fn(&'config [u8]) -> Option<T>,
292 ) -> Result<Option<T>, ConfigValueParseError> {
297 ) -> Result<Option<T>, ConfigValueParseError> {
293 match self.get_inner(&section, &item) {
298 match self.get_inner(&section, &item) {
294 Some((layer, v)) => match parse(&v.bytes) {
299 Some((layer, v)) => match parse(&v.bytes) {
295 Some(b) => Ok(Some(b)),
300 Some(b) => Ok(Some(b)),
296 None => Err(ConfigValueParseError {
301 None => Err(ConfigValueParseError {
297 origin: layer.origin.to_owned(),
302 origin: layer.origin.to_owned(),
298 line: v.line,
303 line: v.line,
299 value: v.bytes.to_owned(),
304 value: v.bytes.to_owned(),
300 section: section.to_owned(),
305 section: section.to_owned(),
301 item: item.to_owned(),
306 item: item.to_owned(),
302 expected_type,
307 expected_type,
303 }),
308 }),
304 },
309 },
305 None => Ok(None),
310 None => Ok(None),
306 }
311 }
307 }
312 }
308
313
309 /// Returns an `Err` if the first value found is not a valid UTF-8 string.
314 /// Returns an `Err` if the first value found is not a valid UTF-8 string.
310 /// Otherwise, returns an `Ok(value)` if found, or `None`.
315 /// Otherwise, returns an `Ok(value)` if found, or `None`.
311 pub fn get_str(
316 pub fn get_str(
312 &self,
317 &self,
313 section: &[u8],
318 section: &[u8],
314 item: &[u8],
319 item: &[u8],
315 ) -> Result<Option<&str>, ConfigValueParseError> {
320 ) -> Result<Option<&str>, ConfigValueParseError> {
316 self.get_parse(section, item, "ASCII or UTF-8 string", |value| {
321 self.get_parse(section, item, "ASCII or UTF-8 string", |value| {
317 str::from_utf8(value).ok()
322 str::from_utf8(value).ok()
318 })
323 })
319 }
324 }
320
325
321 /// Returns an `Err` if the first value found is not a valid unsigned
326 /// Returns an `Err` if the first value found is not a valid unsigned
322 /// integer. Otherwise, returns an `Ok(value)` if found, or `None`.
327 /// integer. Otherwise, returns an `Ok(value)` if found, or `None`.
323 pub fn get_u32(
328 pub fn get_u32(
324 &self,
329 &self,
325 section: &[u8],
330 section: &[u8],
326 item: &[u8],
331 item: &[u8],
327 ) -> Result<Option<u32>, ConfigValueParseError> {
332 ) -> Result<Option<u32>, ConfigValueParseError> {
328 self.get_parse(section, item, "valid integer", |value| {
333 self.get_parse(section, item, "valid integer", |value| {
329 str::from_utf8(value).ok()?.parse().ok()
334 str::from_utf8(value).ok()?.parse().ok()
330 })
335 })
331 }
336 }
332
337
333 /// Returns an `Err` if the first value found is not a valid file size
338 /// Returns an `Err` if the first value found is not a valid file size
334 /// value such as `30` (default unit is bytes), `7 MB`, or `42.5 kb`.
339 /// value such as `30` (default unit is bytes), `7 MB`, or `42.5 kb`.
335 /// Otherwise, returns an `Ok(value_in_bytes)` if found, or `None`.
340 /// Otherwise, returns an `Ok(value_in_bytes)` if found, or `None`.
336 pub fn get_byte_size(
341 pub fn get_byte_size(
337 &self,
342 &self,
338 section: &[u8],
343 section: &[u8],
339 item: &[u8],
344 item: &[u8],
340 ) -> Result<Option<u64>, ConfigValueParseError> {
345 ) -> Result<Option<u64>, ConfigValueParseError> {
341 self.get_parse(section, item, "byte quantity", values::parse_byte_size)
346 self.get_parse(section, item, "byte quantity", values::parse_byte_size)
342 }
347 }
343
348
344 /// Returns an `Err` if the first value found is not a valid boolean.
349 /// Returns an `Err` if the first value found is not a valid boolean.
345 /// Otherwise, returns an `Ok(option)`, where `option` is the boolean if
350 /// Otherwise, returns an `Ok(option)`, where `option` is the boolean if
346 /// found, or `None`.
351 /// found, or `None`.
347 pub fn get_option(
352 pub fn get_option(
348 &self,
353 &self,
349 section: &[u8],
354 section: &[u8],
350 item: &[u8],
355 item: &[u8],
351 ) -> Result<Option<bool>, ConfigValueParseError> {
356 ) -> Result<Option<bool>, ConfigValueParseError> {
352 self.get_parse(section, item, "boolean", values::parse_bool)
357 self.get_parse(section, item, "boolean", values::parse_bool)
353 }
358 }
354
359
355 /// Returns the corresponding boolean in the config. Returns `Ok(false)`
360 /// Returns the corresponding boolean in the config. Returns `Ok(false)`
356 /// if the value is not found, an `Err` if it's not a valid boolean.
361 /// if the value is not found, an `Err` if it's not a valid boolean.
357 pub fn get_bool(
362 pub fn get_bool(
358 &self,
363 &self,
359 section: &[u8],
364 section: &[u8],
360 item: &[u8],
365 item: &[u8],
361 ) -> Result<bool, ConfigValueParseError> {
366 ) -> Result<bool, ConfigValueParseError> {
362 Ok(self.get_option(section, item)?.unwrap_or(false))
367 Ok(self.get_option(section, item)?.unwrap_or(false))
363 }
368 }
364
369
365 /// Returns `true` if the extension is enabled, `false` otherwise
370 /// Returns `true` if the extension is enabled, `false` otherwise
366 pub fn is_extension_enabled(&self, extension: &[u8]) -> bool {
371 pub fn is_extension_enabled(&self, extension: &[u8]) -> bool {
367 let value = self.get(b"extensions", extension);
372 let value = self.get(b"extensions", extension);
368 match value {
373 match value {
369 Some(c) => !c.starts_with(b"!"),
374 Some(c) => !c.starts_with(b"!"),
370 None => false,
375 None => false,
371 }
376 }
372 }
377 }
373
378
374 /// If there is an `item` value in `section`, parse and return a list of
379 /// If there is an `item` value in `section`, parse and return a list of
375 /// byte strings.
380 /// byte strings.
376 pub fn get_list(
381 pub fn get_list(
377 &self,
382 &self,
378 section: &[u8],
383 section: &[u8],
379 item: &[u8],
384 item: &[u8],
380 ) -> Option<Vec<Vec<u8>>> {
385 ) -> Option<Vec<Vec<u8>>> {
381 self.get(section, item).map(values::parse_list)
386 self.get(section, item).map(values::parse_list)
382 }
387 }
383
388
384 /// Returns the raw value bytes of the first one found, or `None`.
389 /// Returns the raw value bytes of the first one found, or `None`.
385 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&[u8]> {
390 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&[u8]> {
386 self.get_inner(section, item)
391 self.get_inner(section, item)
387 .map(|(_, value)| value.bytes.as_ref())
392 .map(|(_, value)| value.bytes.as_ref())
388 }
393 }
389
394
390 /// Returns the layer and the value of the first one found, or `None`.
395 /// Returns the layer and the value of the first one found, or `None`.
391 fn get_inner(
396 fn get_inner(
392 &self,
397 &self,
393 section: &[u8],
398 section: &[u8],
394 item: &[u8],
399 item: &[u8],
395 ) -> Option<(&ConfigLayer, &ConfigValue)> {
400 ) -> Option<(&ConfigLayer, &ConfigValue)> {
396 for layer in self.layers.iter().rev() {
401 for layer in self.layers.iter().rev() {
397 if !layer.trusted {
402 if !layer.trusted {
398 continue;
403 continue;
399 }
404 }
400 if let Some(v) = layer.get(&section, &item) {
405 if let Some(v) = layer.get(&section, &item) {
401 return Some((&layer, v));
406 return Some((&layer, v));
402 }
407 }
403 }
408 }
404 None
409 None
405 }
410 }
406
411
407 /// Return all keys defined for the given section
412 /// Return all keys defined for the given section
408 pub fn get_section_keys(&self, section: &[u8]) -> HashSet<&[u8]> {
413 pub fn get_section_keys(&self, section: &[u8]) -> HashSet<&[u8]> {
409 self.layers
414 self.layers
410 .iter()
415 .iter()
411 .flat_map(|layer| layer.iter_keys(section))
416 .flat_map(|layer| layer.iter_keys(section))
412 .collect()
417 .collect()
413 }
418 }
414
419
415 /// Returns whether any key is defined in the given section
420 /// Returns whether any key is defined in the given section
416 pub fn has_non_empty_section(&self, section: &[u8]) -> bool {
421 pub fn has_non_empty_section(&self, section: &[u8]) -> bool {
417 self.layers
422 self.layers
418 .iter()
423 .iter()
419 .any(|layer| layer.has_non_empty_section(section))
424 .any(|layer| layer.has_non_empty_section(section))
420 }
425 }
421
426
422 /// Yields (key, value) pairs for everything in the given section
427 /// Yields (key, value) pairs for everything in the given section
423 pub fn iter_section<'a>(
428 pub fn iter_section<'a>(
424 &'a self,
429 &'a self,
425 section: &'a [u8],
430 section: &'a [u8],
426 ) -> impl Iterator<Item = (&[u8], &[u8])> + 'a {
431 ) -> impl Iterator<Item = (&[u8], &[u8])> + 'a {
427 // TODO: Use `Iterator`’s `.peekable()` when its `peek_mut` is
432 // TODO: Use `Iterator`’s `.peekable()` when its `peek_mut` is
428 // available:
433 // available:
429 // https://doc.rust-lang.org/nightly/std/iter/struct.Peekable.html#method.peek_mut
434 // https://doc.rust-lang.org/nightly/std/iter/struct.Peekable.html#method.peek_mut
430 struct Peekable<I: Iterator> {
435 struct Peekable<I: Iterator> {
431 iter: I,
436 iter: I,
432 /// Remember a peeked value, even if it was None.
437 /// Remember a peeked value, even if it was None.
433 peeked: Option<Option<I::Item>>,
438 peeked: Option<Option<I::Item>>,
434 }
439 }
435
440
436 impl<I: Iterator> Peekable<I> {
441 impl<I: Iterator> Peekable<I> {
437 fn new(iter: I) -> Self {
442 fn new(iter: I) -> Self {
438 Self { iter, peeked: None }
443 Self { iter, peeked: None }
439 }
444 }
440
445
441 fn next(&mut self) {
446 fn next(&mut self) {
442 self.peeked = None
447 self.peeked = None
443 }
448 }
444
449
445 fn peek_mut(&mut self) -> Option<&mut I::Item> {
450 fn peek_mut(&mut self) -> Option<&mut I::Item> {
446 let iter = &mut self.iter;
451 let iter = &mut self.iter;
447 self.peeked.get_or_insert_with(|| iter.next()).as_mut()
452 self.peeked.get_or_insert_with(|| iter.next()).as_mut()
448 }
453 }
449 }
454 }
450
455
451 // Deduplicate keys redefined in multiple layers
456 // Deduplicate keys redefined in multiple layers
452 let mut keys_already_seen = HashSet::new();
457 let mut keys_already_seen = HashSet::new();
453 let mut key_is_new =
458 let mut key_is_new =
454 move |&(key, _value): &(&'a [u8], &'a [u8])| -> bool {
459 move |&(key, _value): &(&'a [u8], &'a [u8])| -> bool {
455 keys_already_seen.insert(key)
460 keys_already_seen.insert(key)
456 };
461 };
457 // This is similar to `flat_map` + `filter_map`, except with a single
462 // This is similar to `flat_map` + `filter_map`, except with a single
458 // closure that owns `key_is_new` (and therefore the
463 // closure that owns `key_is_new` (and therefore the
459 // `keys_already_seen` set):
464 // `keys_already_seen` set):
460 let mut layer_iters = Peekable::new(
465 let mut layer_iters = Peekable::new(
461 self.layers
466 self.layers
462 .iter()
467 .iter()
463 .rev()
468 .rev()
464 .map(move |layer| layer.iter_section(section)),
469 .map(move |layer| layer.iter_section(section)),
465 );
470 );
466 std::iter::from_fn(move || loop {
471 std::iter::from_fn(move || loop {
467 if let Some(pair) = layer_iters.peek_mut()?.find(&mut key_is_new) {
472 if let Some(pair) = layer_iters.peek_mut()?.find(&mut key_is_new) {
468 return Some(pair);
473 return Some(pair);
469 } else {
474 } else {
470 layer_iters.next();
475 layer_iters.next();
471 }
476 }
472 })
477 })
473 }
478 }
474
479
475 /// Get raw values bytes from all layers (even untrusted ones) in order
480 /// Get raw values bytes from all layers (even untrusted ones) in order
476 /// of precedence.
481 /// of precedence.
477 #[cfg(test)]
482 #[cfg(test)]
478 fn get_all(&self, section: &[u8], item: &[u8]) -> Vec<&[u8]> {
483 fn get_all(&self, section: &[u8], item: &[u8]) -> Vec<&[u8]> {
479 let mut res = vec![];
484 let mut res = vec![];
480 for layer in self.layers.iter().rev() {
485 for layer in self.layers.iter().rev() {
481 if let Some(v) = layer.get(&section, &item) {
486 if let Some(v) = layer.get(&section, &item) {
482 res.push(v.bytes.as_ref());
487 res.push(v.bytes.as_ref());
483 }
488 }
484 }
489 }
485 res
490 res
486 }
491 }
487 }
492 }
488
493
489 #[cfg(test)]
494 #[cfg(test)]
490 mod tests {
495 mod tests {
491 use super::*;
496 use super::*;
492 use pretty_assertions::assert_eq;
497 use pretty_assertions::assert_eq;
493 use std::fs::File;
498 use std::fs::File;
494 use std::io::Write;
499 use std::io::Write;
495
500
496 #[test]
501 #[test]
497 fn test_include_layer_ordering() {
502 fn test_include_layer_ordering() {
498 let tmpdir = tempfile::tempdir().unwrap();
503 let tmpdir = tempfile::tempdir().unwrap();
499 let tmpdir_path = tmpdir.path();
504 let tmpdir_path = tmpdir.path();
500 let mut included_file =
505 let mut included_file =
501 File::create(&tmpdir_path.join("included.rc")).unwrap();
506 File::create(&tmpdir_path.join("included.rc")).unwrap();
502
507
503 included_file.write_all(b"[section]\nitem=value1").unwrap();
508 included_file.write_all(b"[section]\nitem=value1").unwrap();
504 let base_config_path = tmpdir_path.join("base.rc");
509 let base_config_path = tmpdir_path.join("base.rc");
505 let mut config_file = File::create(&base_config_path).unwrap();
510 let mut config_file = File::create(&base_config_path).unwrap();
506 let data =
511 let data =
507 b"[section]\nitem=value0\n%include included.rc\nitem=value2\n\
512 b"[section]\nitem=value0\n%include included.rc\nitem=value2\n\
508 [section2]\ncount = 4\nsize = 1.5 KB\nnot-count = 1.5\nnot-size = 1 ub";
513 [section2]\ncount = 4\nsize = 1.5 KB\nnot-count = 1.5\nnot-size = 1 ub";
509 config_file.write_all(data).unwrap();
514 config_file.write_all(data).unwrap();
510
515
511 let sources = vec![ConfigSource::AbsPath(base_config_path)];
516 let sources = vec![ConfigSource::AbsPath(base_config_path)];
512 let config = Config::load_from_explicit_sources(sources)
517 let config = Config::load_from_explicit_sources(sources)
513 .expect("expected valid config");
518 .expect("expected valid config");
514
519
515 let (_, value) = config.get_inner(b"section", b"item").unwrap();
520 let (_, value) = config.get_inner(b"section", b"item").unwrap();
516 assert_eq!(
521 assert_eq!(
517 value,
522 value,
518 &ConfigValue {
523 &ConfigValue {
519 bytes: b"value2".to_vec(),
524 bytes: b"value2".to_vec(),
520 line: Some(4)
525 line: Some(4)
521 }
526 }
522 );
527 );
523
528
524 let value = config.get(b"section", b"item").unwrap();
529 let value = config.get(b"section", b"item").unwrap();
525 assert_eq!(value, b"value2",);
530 assert_eq!(value, b"value2",);
526 assert_eq!(
531 assert_eq!(
527 config.get_all(b"section", b"item"),
532 config.get_all(b"section", b"item"),
528 [b"value2", b"value1", b"value0"]
533 [b"value2", b"value1", b"value0"]
529 );
534 );
530
535
531 assert_eq!(config.get_u32(b"section2", b"count").unwrap(), Some(4));
536 assert_eq!(config.get_u32(b"section2", b"count").unwrap(), Some(4));
532 assert_eq!(
537 assert_eq!(
533 config.get_byte_size(b"section2", b"size").unwrap(),
538 config.get_byte_size(b"section2", b"size").unwrap(),
534 Some(1024 + 512)
539 Some(1024 + 512)
535 );
540 );
536 assert!(config.get_u32(b"section2", b"not-count").is_err());
541 assert!(config.get_u32(b"section2", b"not-count").is_err());
537 assert!(config.get_byte_size(b"section2", b"not-size").is_err());
542 assert!(config.get_byte_size(b"section2", b"not-size").is_err());
538 }
543 }
539 }
544 }
@@ -1,680 +1,680 b''
1 extern crate log;
1 extern crate log;
2 use crate::error::CommandError;
2 use crate::error::CommandError;
3 use crate::ui::Ui;
3 use crate::ui::Ui;
4 use clap::App;
4 use clap::App;
5 use clap::AppSettings;
5 use clap::AppSettings;
6 use clap::Arg;
6 use clap::Arg;
7 use clap::ArgMatches;
7 use clap::ArgMatches;
8 use format_bytes::{format_bytes, join};
8 use format_bytes::{format_bytes, join};
9 use hg::config::{Config, ConfigSource};
9 use hg::config::{Config, ConfigSource};
10 use hg::exit_codes;
10 use hg::exit_codes;
11 use hg::repo::{Repo, RepoError};
11 use hg::repo::{Repo, RepoError};
12 use hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes};
12 use hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes};
13 use hg::utils::SliceExt;
13 use hg::utils::SliceExt;
14 use std::collections::HashSet;
14 use std::collections::HashSet;
15 use std::ffi::OsString;
15 use std::ffi::OsString;
16 use std::path::PathBuf;
16 use std::path::PathBuf;
17 use std::process::Command;
17 use std::process::Command;
18
18
19 mod blackbox;
19 mod blackbox;
20 mod error;
20 mod error;
21 mod ui;
21 mod ui;
22 pub mod utils {
22 pub mod utils {
23 pub mod path_utils;
23 pub mod path_utils;
24 }
24 }
25
25
26 fn main_with_result(
26 fn main_with_result(
27 process_start_time: &blackbox::ProcessStartTime,
27 process_start_time: &blackbox::ProcessStartTime,
28 ui: &ui::Ui,
28 ui: &ui::Ui,
29 repo: Result<&Repo, &NoRepoInCwdError>,
29 repo: Result<&Repo, &NoRepoInCwdError>,
30 config: &Config,
30 config: &Config,
31 ) -> Result<(), CommandError> {
31 ) -> Result<(), CommandError> {
32 check_unsupported(config, repo, ui)?;
32 check_unsupported(config, repo, ui)?;
33
33
34 let app = App::new("rhg")
34 let app = App::new("rhg")
35 .global_setting(AppSettings::AllowInvalidUtf8)
35 .global_setting(AppSettings::AllowInvalidUtf8)
36 .global_setting(AppSettings::DisableVersion)
36 .global_setting(AppSettings::DisableVersion)
37 .setting(AppSettings::SubcommandRequired)
37 .setting(AppSettings::SubcommandRequired)
38 .setting(AppSettings::VersionlessSubcommands)
38 .setting(AppSettings::VersionlessSubcommands)
39 .arg(
39 .arg(
40 Arg::with_name("repository")
40 Arg::with_name("repository")
41 .help("repository root directory")
41 .help("repository root directory")
42 .short("-R")
42 .short("-R")
43 .long("--repository")
43 .long("--repository")
44 .value_name("REPO")
44 .value_name("REPO")
45 .takes_value(true)
45 .takes_value(true)
46 // Both ok: `hg -R ./foo log` or `hg log -R ./foo`
46 // Both ok: `hg -R ./foo log` or `hg log -R ./foo`
47 .global(true),
47 .global(true),
48 )
48 )
49 .arg(
49 .arg(
50 Arg::with_name("config")
50 Arg::with_name("config")
51 .help("set/override config option (use 'section.name=value')")
51 .help("set/override config option (use 'section.name=value')")
52 .long("--config")
52 .long("--config")
53 .value_name("CONFIG")
53 .value_name("CONFIG")
54 .takes_value(true)
54 .takes_value(true)
55 .global(true)
55 .global(true)
56 // Ok: `--config section.key1=val --config section.key2=val2`
56 // Ok: `--config section.key1=val --config section.key2=val2`
57 .multiple(true)
57 .multiple(true)
58 // Not ok: `--config section.key1=val section.key2=val2`
58 // Not ok: `--config section.key1=val section.key2=val2`
59 .number_of_values(1),
59 .number_of_values(1),
60 )
60 )
61 .arg(
61 .arg(
62 Arg::with_name("cwd")
62 Arg::with_name("cwd")
63 .help("change working directory")
63 .help("change working directory")
64 .long("--cwd")
64 .long("--cwd")
65 .value_name("DIR")
65 .value_name("DIR")
66 .takes_value(true)
66 .takes_value(true)
67 .global(true),
67 .global(true),
68 )
68 )
69 .version("0.0.1");
69 .version("0.0.1");
70 let app = add_subcommand_args(app);
70 let app = add_subcommand_args(app);
71
71
72 let matches = app.clone().get_matches_safe()?;
72 let matches = app.clone().get_matches_safe()?;
73
73
74 let (subcommand_name, subcommand_matches) = matches.subcommand();
74 let (subcommand_name, subcommand_matches) = matches.subcommand();
75
75
76 // Mercurial allows users to define "defaults" for commands, fallback
76 // Mercurial allows users to define "defaults" for commands, fallback
77 // if a default is detected for the current command
77 // if a default is detected for the current command
78 let defaults = config.get_str(b"defaults", subcommand_name.as_bytes());
78 let defaults = config.get_str(b"defaults", subcommand_name.as_bytes());
79 if defaults?.is_some() {
79 if defaults?.is_some() {
80 let msg = "`defaults` config set";
80 let msg = "`defaults` config set";
81 return Err(CommandError::unsupported(msg));
81 return Err(CommandError::unsupported(msg));
82 }
82 }
83
83
84 for prefix in ["pre", "post", "fail"].iter() {
84 for prefix in ["pre", "post", "fail"].iter() {
85 // Mercurial allows users to define generic hooks for commands,
85 // Mercurial allows users to define generic hooks for commands,
86 // fallback if any are detected
86 // fallback if any are detected
87 let item = format!("{}-{}", prefix, subcommand_name);
87 let item = format!("{}-{}", prefix, subcommand_name);
88 let hook_for_command = config.get_str(b"hooks", item.as_bytes())?;
88 let hook_for_command = config.get_str(b"hooks", item.as_bytes())?;
89 if hook_for_command.is_some() {
89 if hook_for_command.is_some() {
90 let msg = format!("{}-{} hook defined", prefix, subcommand_name);
90 let msg = format!("{}-{} hook defined", prefix, subcommand_name);
91 return Err(CommandError::unsupported(msg));
91 return Err(CommandError::unsupported(msg));
92 }
92 }
93 }
93 }
94 let run = subcommand_run_fn(subcommand_name)
94 let run = subcommand_run_fn(subcommand_name)
95 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
95 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
96 let subcommand_args = subcommand_matches
96 let subcommand_args = subcommand_matches
97 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
97 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
98
98
99 let invocation = CliInvocation {
99 let invocation = CliInvocation {
100 ui,
100 ui,
101 subcommand_args,
101 subcommand_args,
102 config,
102 config,
103 repo,
103 repo,
104 };
104 };
105
105
106 if let Ok(repo) = repo {
106 if let Ok(repo) = repo {
107 // We don't support subrepos, fallback if the subrepos file is present
107 // We don't support subrepos, fallback if the subrepos file is present
108 if repo.working_directory_vfs().join(".hgsub").exists() {
108 if repo.working_directory_vfs().join(".hgsub").exists() {
109 let msg = "subrepos (.hgsub is present)";
109 let msg = "subrepos (.hgsub is present)";
110 return Err(CommandError::unsupported(msg));
110 return Err(CommandError::unsupported(msg));
111 }
111 }
112 }
112 }
113
113
114 if config.is_extension_enabled(b"blackbox") {
114 if config.is_extension_enabled(b"blackbox") {
115 let blackbox =
115 let blackbox =
116 blackbox::Blackbox::new(&invocation, process_start_time)?;
116 blackbox::Blackbox::new(&invocation, process_start_time)?;
117 blackbox.log_command_start();
117 blackbox.log_command_start();
118 let result = run(&invocation);
118 let result = run(&invocation);
119 blackbox.log_command_end(exit_code(
119 blackbox.log_command_end(exit_code(
120 &result,
120 &result,
121 // TODO: show a warning or combine with original error if
121 // TODO: show a warning or combine with original error if
122 // `get_bool` returns an error
122 // `get_bool` returns an error
123 config
123 config
124 .get_bool(b"ui", b"detailed-exit-code")
124 .get_bool(b"ui", b"detailed-exit-code")
125 .unwrap_or(false),
125 .unwrap_or(false),
126 ));
126 ));
127 result
127 result
128 } else {
128 } else {
129 run(&invocation)
129 run(&invocation)
130 }
130 }
131 }
131 }
132
132
133 fn main() {
133 fn main() {
134 // Run this first, before we find out if the blackbox extension is even
134 // Run this first, before we find out if the blackbox extension is even
135 // enabled, in order to include everything in-between in the duration
135 // enabled, in order to include everything in-between in the duration
136 // measurements. Reading config files can be slow if they’re on NFS.
136 // measurements. Reading config files can be slow if they’re on NFS.
137 let process_start_time = blackbox::ProcessStartTime::now();
137 let process_start_time = blackbox::ProcessStartTime::now();
138
138
139 env_logger::init();
139 env_logger::init();
140 let ui = ui::Ui::new();
141
140
142 let early_args = EarlyArgs::parse(std::env::args_os());
141 let early_args = EarlyArgs::parse(std::env::args_os());
143
142
144 let initial_current_dir = early_args.cwd.map(|cwd| {
143 let initial_current_dir = early_args.cwd.map(|cwd| {
145 let cwd = get_path_from_bytes(&cwd);
144 let cwd = get_path_from_bytes(&cwd);
146 std::env::current_dir()
145 std::env::current_dir()
147 .and_then(|initial| {
146 .and_then(|initial| {
148 std::env::set_current_dir(cwd)?;
147 std::env::set_current_dir(cwd)?;
149 Ok(initial)
148 Ok(initial)
150 })
149 })
151 .unwrap_or_else(|error| {
150 .unwrap_or_else(|error| {
152 exit(
151 exit(
153 &None,
152 &None,
154 &ui,
153 &Ui::new(&Config::empty()),
155 OnUnsupported::Abort,
154 OnUnsupported::Abort,
156 Err(CommandError::abort(format!(
155 Err(CommandError::abort(format!(
157 "abort: {}: '{}'",
156 "abort: {}: '{}'",
158 error,
157 error,
159 cwd.display()
158 cwd.display()
160 ))),
159 ))),
161 false,
160 false,
162 )
161 )
163 })
162 })
164 });
163 });
165
164
166 let mut non_repo_config =
165 let mut non_repo_config =
167 Config::load_non_repo().unwrap_or_else(|error| {
166 Config::load_non_repo().unwrap_or_else(|error| {
168 // Normally this is decided based on config, but we don’t have that
167 // Normally this is decided based on config, but we don’t have that
169 // available. As of this writing config loading never returns an
168 // available. As of this writing config loading never returns an
170 // "unsupported" error but that is not enforced by the type system.
169 // "unsupported" error but that is not enforced by the type system.
171 let on_unsupported = OnUnsupported::Abort;
170 let on_unsupported = OnUnsupported::Abort;
172
171
173 exit(
172 exit(
174 &initial_current_dir,
173 &initial_current_dir,
175 &ui,
174 &Ui::new(&Config::empty()),
176 on_unsupported,
175 on_unsupported,
177 Err(error.into()),
176 Err(error.into()),
178 false,
177 false,
179 )
178 )
180 });
179 });
181
180
182 non_repo_config
181 non_repo_config
183 .load_cli_args_config(early_args.config)
182 .load_cli_args_config(early_args.config)
184 .unwrap_or_else(|error| {
183 .unwrap_or_else(|error| {
185 exit(
184 exit(
186 &initial_current_dir,
185 &initial_current_dir,
187 &ui,
186 &Ui::new(&non_repo_config),
188 OnUnsupported::from_config(&non_repo_config),
187 OnUnsupported::from_config(&non_repo_config),
189 Err(error.into()),
188 Err(error.into()),
190 non_repo_config
189 non_repo_config
191 .get_bool(b"ui", b"detailed-exit-code")
190 .get_bool(b"ui", b"detailed-exit-code")
192 .unwrap_or(false),
191 .unwrap_or(false),
193 )
192 )
194 });
193 });
195
194
196 if let Some(repo_path_bytes) = &early_args.repo {
195 if let Some(repo_path_bytes) = &early_args.repo {
197 lazy_static::lazy_static! {
196 lazy_static::lazy_static! {
198 static ref SCHEME_RE: regex::bytes::Regex =
197 static ref SCHEME_RE: regex::bytes::Regex =
199 // Same as `_matchscheme` in `mercurial/util.py`
198 // Same as `_matchscheme` in `mercurial/util.py`
200 regex::bytes::Regex::new("^[a-zA-Z0-9+.\\-]+:").unwrap();
199 regex::bytes::Regex::new("^[a-zA-Z0-9+.\\-]+:").unwrap();
201 }
200 }
202 if SCHEME_RE.is_match(&repo_path_bytes) {
201 if SCHEME_RE.is_match(&repo_path_bytes) {
203 exit(
202 exit(
204 &initial_current_dir,
203 &initial_current_dir,
205 &ui,
204 &Ui::new(&non_repo_config),
206 OnUnsupported::from_config(&non_repo_config),
205 OnUnsupported::from_config(&non_repo_config),
207 Err(CommandError::UnsupportedFeature {
206 Err(CommandError::UnsupportedFeature {
208 message: format_bytes!(
207 message: format_bytes!(
209 b"URL-like --repository {}",
208 b"URL-like --repository {}",
210 repo_path_bytes
209 repo_path_bytes
211 ),
210 ),
212 }),
211 }),
213 // TODO: show a warning or combine with original error if
212 // TODO: show a warning or combine with original error if
214 // `get_bool` returns an error
213 // `get_bool` returns an error
215 non_repo_config
214 non_repo_config
216 .get_bool(b"ui", b"detailed-exit-code")
215 .get_bool(b"ui", b"detailed-exit-code")
217 .unwrap_or(false),
216 .unwrap_or(false),
218 )
217 )
219 }
218 }
220 }
219 }
221 let repo_arg = early_args.repo.unwrap_or(Vec::new());
220 let repo_arg = early_args.repo.unwrap_or(Vec::new());
222 let repo_path: Option<PathBuf> = {
221 let repo_path: Option<PathBuf> = {
223 if repo_arg.is_empty() {
222 if repo_arg.is_empty() {
224 None
223 None
225 } else {
224 } else {
226 let local_config = {
225 let local_config = {
227 if std::env::var_os("HGRCSKIPREPO").is_none() {
226 if std::env::var_os("HGRCSKIPREPO").is_none() {
228 // TODO: handle errors from find_repo_root
227 // TODO: handle errors from find_repo_root
229 if let Ok(current_dir_path) = Repo::find_repo_root() {
228 if let Ok(current_dir_path) = Repo::find_repo_root() {
230 let config_files = vec![
229 let config_files = vec![
231 ConfigSource::AbsPath(
230 ConfigSource::AbsPath(
232 current_dir_path.join(".hg/hgrc"),
231 current_dir_path.join(".hg/hgrc"),
233 ),
232 ),
234 ConfigSource::AbsPath(
233 ConfigSource::AbsPath(
235 current_dir_path.join(".hg/hgrc-not-shared"),
234 current_dir_path.join(".hg/hgrc-not-shared"),
236 ),
235 ),
237 ];
236 ];
238 // TODO: handle errors from
237 // TODO: handle errors from
239 // `load_from_explicit_sources`
238 // `load_from_explicit_sources`
240 Config::load_from_explicit_sources(config_files).ok()
239 Config::load_from_explicit_sources(config_files).ok()
241 } else {
240 } else {
242 None
241 None
243 }
242 }
244 } else {
243 } else {
245 None
244 None
246 }
245 }
247 };
246 };
248
247
249 let non_repo_config_val = {
248 let non_repo_config_val = {
250 let non_repo_val = non_repo_config.get(b"paths", &repo_arg);
249 let non_repo_val = non_repo_config.get(b"paths", &repo_arg);
251 match &non_repo_val {
250 match &non_repo_val {
252 Some(val) if val.len() > 0 => home::home_dir()
251 Some(val) if val.len() > 0 => home::home_dir()
253 .unwrap_or_else(|| PathBuf::from("~"))
252 .unwrap_or_else(|| PathBuf::from("~"))
254 .join(get_path_from_bytes(val))
253 .join(get_path_from_bytes(val))
255 .canonicalize()
254 .canonicalize()
256 // TODO: handle error and make it similar to python
255 // TODO: handle error and make it similar to python
257 // implementation maybe?
256 // implementation maybe?
258 .ok(),
257 .ok(),
259 _ => None,
258 _ => None,
260 }
259 }
261 };
260 };
262
261
263 let config_val = match &local_config {
262 let config_val = match &local_config {
264 None => non_repo_config_val,
263 None => non_repo_config_val,
265 Some(val) => {
264 Some(val) => {
266 let local_config_val = val.get(b"paths", &repo_arg);
265 let local_config_val = val.get(b"paths", &repo_arg);
267 match &local_config_val {
266 match &local_config_val {
268 Some(val) if val.len() > 0 => {
267 Some(val) if val.len() > 0 => {
269 // presence of a local_config assures that
268 // presence of a local_config assures that
270 // current_dir
269 // current_dir
271 // wont result in an Error
270 // wont result in an Error
272 let canpath = hg::utils::current_dir()
271 let canpath = hg::utils::current_dir()
273 .unwrap()
272 .unwrap()
274 .join(get_path_from_bytes(val))
273 .join(get_path_from_bytes(val))
275 .canonicalize();
274 .canonicalize();
276 canpath.ok().or(non_repo_config_val)
275 canpath.ok().or(non_repo_config_val)
277 }
276 }
278 _ => non_repo_config_val,
277 _ => non_repo_config_val,
279 }
278 }
280 }
279 }
281 };
280 };
282 config_val.or(Some(get_path_from_bytes(&repo_arg).to_path_buf()))
281 config_val.or(Some(get_path_from_bytes(&repo_arg).to_path_buf()))
283 }
282 }
284 };
283 };
285
284
286 let repo_result = match Repo::find(&non_repo_config, repo_path.to_owned())
285 let repo_result = match Repo::find(&non_repo_config, repo_path.to_owned())
287 {
286 {
288 Ok(repo) => Ok(repo),
287 Ok(repo) => Ok(repo),
289 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
288 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
290 // Not finding a repo is not fatal yet, if `-R` was not given
289 // Not finding a repo is not fatal yet, if `-R` was not given
291 Err(NoRepoInCwdError { cwd: at })
290 Err(NoRepoInCwdError { cwd: at })
292 }
291 }
293 Err(error) => exit(
292 Err(error) => exit(
294 &initial_current_dir,
293 &initial_current_dir,
295 &ui,
294 &Ui::new(&non_repo_config),
296 OnUnsupported::from_config(&non_repo_config),
295 OnUnsupported::from_config(&non_repo_config),
297 Err(error.into()),
296 Err(error.into()),
298 // TODO: show a warning or combine with original error if
297 // TODO: show a warning or combine with original error if
299 // `get_bool` returns an error
298 // `get_bool` returns an error
300 non_repo_config
299 non_repo_config
301 .get_bool(b"ui", b"detailed-exit-code")
300 .get_bool(b"ui", b"detailed-exit-code")
302 .unwrap_or(false),
301 .unwrap_or(false),
303 ),
302 ),
304 };
303 };
305
304
306 let config = if let Ok(repo) = &repo_result {
305 let config = if let Ok(repo) = &repo_result {
307 repo.config()
306 repo.config()
308 } else {
307 } else {
309 &non_repo_config
308 &non_repo_config
310 };
309 };
310 let ui = Ui::new(&config);
311 let on_unsupported = OnUnsupported::from_config(config);
311 let on_unsupported = OnUnsupported::from_config(config);
312
312
313 let result = main_with_result(
313 let result = main_with_result(
314 &process_start_time,
314 &process_start_time,
315 &ui,
315 &ui,
316 repo_result.as_ref(),
316 repo_result.as_ref(),
317 config,
317 config,
318 );
318 );
319 exit(
319 exit(
320 &initial_current_dir,
320 &initial_current_dir,
321 &ui,
321 &ui,
322 on_unsupported,
322 on_unsupported,
323 result,
323 result,
324 // TODO: show a warning or combine with original error if `get_bool`
324 // TODO: show a warning or combine with original error if `get_bool`
325 // returns an error
325 // returns an error
326 config
326 config
327 .get_bool(b"ui", b"detailed-exit-code")
327 .get_bool(b"ui", b"detailed-exit-code")
328 .unwrap_or(false),
328 .unwrap_or(false),
329 )
329 )
330 }
330 }
331
331
332 fn exit_code(
332 fn exit_code(
333 result: &Result<(), CommandError>,
333 result: &Result<(), CommandError>,
334 use_detailed_exit_code: bool,
334 use_detailed_exit_code: bool,
335 ) -> i32 {
335 ) -> i32 {
336 match result {
336 match result {
337 Ok(()) => exit_codes::OK,
337 Ok(()) => exit_codes::OK,
338 Err(CommandError::Abort {
338 Err(CommandError::Abort {
339 message: _,
339 message: _,
340 detailed_exit_code,
340 detailed_exit_code,
341 }) => {
341 }) => {
342 if use_detailed_exit_code {
342 if use_detailed_exit_code {
343 *detailed_exit_code
343 *detailed_exit_code
344 } else {
344 } else {
345 exit_codes::ABORT
345 exit_codes::ABORT
346 }
346 }
347 }
347 }
348 Err(CommandError::Unsuccessful) => exit_codes::UNSUCCESSFUL,
348 Err(CommandError::Unsuccessful) => exit_codes::UNSUCCESSFUL,
349
349
350 // Exit with a specific code and no error message to let a potential
350 // Exit with a specific code and no error message to let a potential
351 // wrapper script fallback to Python-based Mercurial.
351 // wrapper script fallback to Python-based Mercurial.
352 Err(CommandError::UnsupportedFeature { .. }) => {
352 Err(CommandError::UnsupportedFeature { .. }) => {
353 exit_codes::UNIMPLEMENTED
353 exit_codes::UNIMPLEMENTED
354 }
354 }
355 }
355 }
356 }
356 }
357
357
358 fn exit(
358 fn exit(
359 initial_current_dir: &Option<PathBuf>,
359 initial_current_dir: &Option<PathBuf>,
360 ui: &Ui,
360 ui: &Ui,
361 mut on_unsupported: OnUnsupported,
361 mut on_unsupported: OnUnsupported,
362 result: Result<(), CommandError>,
362 result: Result<(), CommandError>,
363 use_detailed_exit_code: bool,
363 use_detailed_exit_code: bool,
364 ) -> ! {
364 ) -> ! {
365 if let (
365 if let (
366 OnUnsupported::Fallback { executable },
366 OnUnsupported::Fallback { executable },
367 Err(CommandError::UnsupportedFeature { .. }),
367 Err(CommandError::UnsupportedFeature { .. }),
368 ) = (&on_unsupported, &result)
368 ) = (&on_unsupported, &result)
369 {
369 {
370 let mut args = std::env::args_os();
370 let mut args = std::env::args_os();
371 let executable = match executable {
371 let executable = match executable {
372 None => {
372 None => {
373 exit_no_fallback(
373 exit_no_fallback(
374 ui,
374 ui,
375 OnUnsupported::Abort,
375 OnUnsupported::Abort,
376 Err(CommandError::abort(
376 Err(CommandError::abort(
377 "abort: 'rhg.on-unsupported=fallback' without \
377 "abort: 'rhg.on-unsupported=fallback' without \
378 'rhg.fallback-executable' set.",
378 'rhg.fallback-executable' set.",
379 )),
379 )),
380 false,
380 false,
381 );
381 );
382 }
382 }
383 Some(executable) => executable,
383 Some(executable) => executable,
384 };
384 };
385 let executable_path = get_path_from_bytes(&executable);
385 let executable_path = get_path_from_bytes(&executable);
386 let this_executable = args.next().expect("exepcted argv[0] to exist");
386 let this_executable = args.next().expect("exepcted argv[0] to exist");
387 if executable_path == &PathBuf::from(this_executable) {
387 if executable_path == &PathBuf::from(this_executable) {
388 // Avoid spawning infinitely many processes until resource
388 // Avoid spawning infinitely many processes until resource
389 // exhaustion.
389 // exhaustion.
390 let _ = ui.write_stderr(&format_bytes!(
390 let _ = ui.write_stderr(&format_bytes!(
391 b"Blocking recursive fallback. The 'rhg.fallback-executable = {}' config \
391 b"Blocking recursive fallback. The 'rhg.fallback-executable = {}' config \
392 points to `rhg` itself.\n",
392 points to `rhg` itself.\n",
393 executable
393 executable
394 ));
394 ));
395 on_unsupported = OnUnsupported::Abort
395 on_unsupported = OnUnsupported::Abort
396 } else {
396 } else {
397 // `args` is now `argv[1..]` since we’ve already consumed
397 // `args` is now `argv[1..]` since we’ve already consumed
398 // `argv[0]`
398 // `argv[0]`
399 let mut command = Command::new(executable_path);
399 let mut command = Command::new(executable_path);
400 command.args(args);
400 command.args(args);
401 if let Some(initial) = initial_current_dir {
401 if let Some(initial) = initial_current_dir {
402 command.current_dir(initial);
402 command.current_dir(initial);
403 }
403 }
404 let result = command.status();
404 let result = command.status();
405 match result {
405 match result {
406 Ok(status) => std::process::exit(
406 Ok(status) => std::process::exit(
407 status.code().unwrap_or(exit_codes::ABORT),
407 status.code().unwrap_or(exit_codes::ABORT),
408 ),
408 ),
409 Err(error) => {
409 Err(error) => {
410 let _ = ui.write_stderr(&format_bytes!(
410 let _ = ui.write_stderr(&format_bytes!(
411 b"tried to fall back to a '{}' sub-process but got error {}\n",
411 b"tried to fall back to a '{}' sub-process but got error {}\n",
412 executable, format_bytes::Utf8(error)
412 executable, format_bytes::Utf8(error)
413 ));
413 ));
414 on_unsupported = OnUnsupported::Abort
414 on_unsupported = OnUnsupported::Abort
415 }
415 }
416 }
416 }
417 }
417 }
418 }
418 }
419 exit_no_fallback(ui, on_unsupported, result, use_detailed_exit_code)
419 exit_no_fallback(ui, on_unsupported, result, use_detailed_exit_code)
420 }
420 }
421
421
422 fn exit_no_fallback(
422 fn exit_no_fallback(
423 ui: &Ui,
423 ui: &Ui,
424 on_unsupported: OnUnsupported,
424 on_unsupported: OnUnsupported,
425 result: Result<(), CommandError>,
425 result: Result<(), CommandError>,
426 use_detailed_exit_code: bool,
426 use_detailed_exit_code: bool,
427 ) -> ! {
427 ) -> ! {
428 match &result {
428 match &result {
429 Ok(_) => {}
429 Ok(_) => {}
430 Err(CommandError::Unsuccessful) => {}
430 Err(CommandError::Unsuccessful) => {}
431 Err(CommandError::Abort {
431 Err(CommandError::Abort {
432 message,
432 message,
433 detailed_exit_code: _,
433 detailed_exit_code: _,
434 }) => {
434 }) => {
435 if !message.is_empty() {
435 if !message.is_empty() {
436 // Ignore errors when writing to stderr, we’re already exiting
436 // Ignore errors when writing to stderr, we’re already exiting
437 // with failure code so there’s not much more we can do.
437 // with failure code so there’s not much more we can do.
438 let _ = ui.write_stderr(&format_bytes!(b"{}\n", message));
438 let _ = ui.write_stderr(&format_bytes!(b"{}\n", message));
439 }
439 }
440 }
440 }
441 Err(CommandError::UnsupportedFeature { message }) => {
441 Err(CommandError::UnsupportedFeature { message }) => {
442 match on_unsupported {
442 match on_unsupported {
443 OnUnsupported::Abort => {
443 OnUnsupported::Abort => {
444 let _ = ui.write_stderr(&format_bytes!(
444 let _ = ui.write_stderr(&format_bytes!(
445 b"unsupported feature: {}\n",
445 b"unsupported feature: {}\n",
446 message
446 message
447 ));
447 ));
448 }
448 }
449 OnUnsupported::AbortSilent => {}
449 OnUnsupported::AbortSilent => {}
450 OnUnsupported::Fallback { .. } => unreachable!(),
450 OnUnsupported::Fallback { .. } => unreachable!(),
451 }
451 }
452 }
452 }
453 }
453 }
454 std::process::exit(exit_code(&result, use_detailed_exit_code))
454 std::process::exit(exit_code(&result, use_detailed_exit_code))
455 }
455 }
456
456
457 macro_rules! subcommands {
457 macro_rules! subcommands {
458 ($( $command: ident )+) => {
458 ($( $command: ident )+) => {
459 mod commands {
459 mod commands {
460 $(
460 $(
461 pub mod $command;
461 pub mod $command;
462 )+
462 )+
463 }
463 }
464
464
465 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
465 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
466 app
466 app
467 $(
467 $(
468 .subcommand(commands::$command::args())
468 .subcommand(commands::$command::args())
469 )+
469 )+
470 }
470 }
471
471
472 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
472 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
473
473
474 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
474 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
475 match name {
475 match name {
476 $(
476 $(
477 stringify!($command) => Some(commands::$command::run),
477 stringify!($command) => Some(commands::$command::run),
478 )+
478 )+
479 _ => None,
479 _ => None,
480 }
480 }
481 }
481 }
482 };
482 };
483 }
483 }
484
484
485 subcommands! {
485 subcommands! {
486 cat
486 cat
487 debugdata
487 debugdata
488 debugrequirements
488 debugrequirements
489 debugignorerhg
489 debugignorerhg
490 files
490 files
491 root
491 root
492 config
492 config
493 status
493 status
494 }
494 }
495
495
496 pub struct CliInvocation<'a> {
496 pub struct CliInvocation<'a> {
497 ui: &'a Ui,
497 ui: &'a Ui,
498 subcommand_args: &'a ArgMatches<'a>,
498 subcommand_args: &'a ArgMatches<'a>,
499 config: &'a Config,
499 config: &'a Config,
500 /// References inside `Result` is a bit peculiar but allow
500 /// References inside `Result` is a bit peculiar but allow
501 /// `invocation.repo?` to work out with `&CliInvocation` since this
501 /// `invocation.repo?` to work out with `&CliInvocation` since this
502 /// `Result` type is `Copy`.
502 /// `Result` type is `Copy`.
503 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
503 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
504 }
504 }
505
505
506 struct NoRepoInCwdError {
506 struct NoRepoInCwdError {
507 cwd: PathBuf,
507 cwd: PathBuf,
508 }
508 }
509
509
510 /// CLI arguments to be parsed "early" in order to be able to read
510 /// CLI arguments to be parsed "early" in order to be able to read
511 /// configuration before using Clap. Ideally we would also use Clap for this,
511 /// configuration before using Clap. Ideally we would also use Clap for this,
512 /// see <https://github.com/clap-rs/clap/discussions/2366>.
512 /// see <https://github.com/clap-rs/clap/discussions/2366>.
513 ///
513 ///
514 /// These arguments are still declared when we do use Clap later, so that Clap
514 /// These arguments are still declared when we do use Clap later, so that Clap
515 /// does not return an error for their presence.
515 /// does not return an error for their presence.
516 struct EarlyArgs {
516 struct EarlyArgs {
517 /// Values of all `--config` arguments. (Possibly none)
517 /// Values of all `--config` arguments. (Possibly none)
518 config: Vec<Vec<u8>>,
518 config: Vec<Vec<u8>>,
519 /// Value of the `-R` or `--repository` argument, if any.
519 /// Value of the `-R` or `--repository` argument, if any.
520 repo: Option<Vec<u8>>,
520 repo: Option<Vec<u8>>,
521 /// Value of the `--cwd` argument, if any.
521 /// Value of the `--cwd` argument, if any.
522 cwd: Option<Vec<u8>>,
522 cwd: Option<Vec<u8>>,
523 }
523 }
524
524
525 impl EarlyArgs {
525 impl EarlyArgs {
526 fn parse(args: impl IntoIterator<Item = OsString>) -> Self {
526 fn parse(args: impl IntoIterator<Item = OsString>) -> Self {
527 let mut args = args.into_iter().map(get_bytes_from_os_str);
527 let mut args = args.into_iter().map(get_bytes_from_os_str);
528 let mut config = Vec::new();
528 let mut config = Vec::new();
529 let mut repo = None;
529 let mut repo = None;
530 let mut cwd = None;
530 let mut cwd = None;
531 // Use `while let` instead of `for` so that we can also call
531 // Use `while let` instead of `for` so that we can also call
532 // `args.next()` inside the loop.
532 // `args.next()` inside the loop.
533 while let Some(arg) = args.next() {
533 while let Some(arg) = args.next() {
534 if arg == b"--config" {
534 if arg == b"--config" {
535 if let Some(value) = args.next() {
535 if let Some(value) = args.next() {
536 config.push(value)
536 config.push(value)
537 }
537 }
538 } else if let Some(value) = arg.drop_prefix(b"--config=") {
538 } else if let Some(value) = arg.drop_prefix(b"--config=") {
539 config.push(value.to_owned())
539 config.push(value.to_owned())
540 }
540 }
541
541
542 if arg == b"--cwd" {
542 if arg == b"--cwd" {
543 if let Some(value) = args.next() {
543 if let Some(value) = args.next() {
544 cwd = Some(value)
544 cwd = Some(value)
545 }
545 }
546 } else if let Some(value) = arg.drop_prefix(b"--cwd=") {
546 } else if let Some(value) = arg.drop_prefix(b"--cwd=") {
547 cwd = Some(value.to_owned())
547 cwd = Some(value.to_owned())
548 }
548 }
549
549
550 if arg == b"--repository" || arg == b"-R" {
550 if arg == b"--repository" || arg == b"-R" {
551 if let Some(value) = args.next() {
551 if let Some(value) = args.next() {
552 repo = Some(value)
552 repo = Some(value)
553 }
553 }
554 } else if let Some(value) = arg.drop_prefix(b"--repository=") {
554 } else if let Some(value) = arg.drop_prefix(b"--repository=") {
555 repo = Some(value.to_owned())
555 repo = Some(value.to_owned())
556 } else if let Some(value) = arg.drop_prefix(b"-R") {
556 } else if let Some(value) = arg.drop_prefix(b"-R") {
557 repo = Some(value.to_owned())
557 repo = Some(value.to_owned())
558 }
558 }
559 }
559 }
560 Self { config, repo, cwd }
560 Self { config, repo, cwd }
561 }
561 }
562 }
562 }
563
563
564 /// What to do when encountering some unsupported feature.
564 /// What to do when encountering some unsupported feature.
565 ///
565 ///
566 /// See `HgError::UnsupportedFeature` and `CommandError::UnsupportedFeature`.
566 /// See `HgError::UnsupportedFeature` and `CommandError::UnsupportedFeature`.
567 enum OnUnsupported {
567 enum OnUnsupported {
568 /// Print an error message describing what feature is not supported,
568 /// Print an error message describing what feature is not supported,
569 /// and exit with code 252.
569 /// and exit with code 252.
570 Abort,
570 Abort,
571 /// Silently exit with code 252.
571 /// Silently exit with code 252.
572 AbortSilent,
572 AbortSilent,
573 /// Try running a Python implementation
573 /// Try running a Python implementation
574 Fallback { executable: Option<Vec<u8>> },
574 Fallback { executable: Option<Vec<u8>> },
575 }
575 }
576
576
577 impl OnUnsupported {
577 impl OnUnsupported {
578 const DEFAULT: Self = OnUnsupported::Abort;
578 const DEFAULT: Self = OnUnsupported::Abort;
579
579
580 fn from_config(config: &Config) -> Self {
580 fn from_config(config: &Config) -> Self {
581 match config
581 match config
582 .get(b"rhg", b"on-unsupported")
582 .get(b"rhg", b"on-unsupported")
583 .map(|value| value.to_ascii_lowercase())
583 .map(|value| value.to_ascii_lowercase())
584 .as_deref()
584 .as_deref()
585 {
585 {
586 Some(b"abort") => OnUnsupported::Abort,
586 Some(b"abort") => OnUnsupported::Abort,
587 Some(b"abort-silent") => OnUnsupported::AbortSilent,
587 Some(b"abort-silent") => OnUnsupported::AbortSilent,
588 Some(b"fallback") => OnUnsupported::Fallback {
588 Some(b"fallback") => OnUnsupported::Fallback {
589 executable: config
589 executable: config
590 .get(b"rhg", b"fallback-executable")
590 .get(b"rhg", b"fallback-executable")
591 .map(|x| x.to_owned()),
591 .map(|x| x.to_owned()),
592 },
592 },
593 None => Self::DEFAULT,
593 None => Self::DEFAULT,
594 Some(_) => {
594 Some(_) => {
595 // TODO: warn about unknown config value
595 // TODO: warn about unknown config value
596 Self::DEFAULT
596 Self::DEFAULT
597 }
597 }
598 }
598 }
599 }
599 }
600 }
600 }
601
601
602 /// The `*` extension is an edge-case for config sub-options that apply to all
602 /// The `*` extension is an edge-case for config sub-options that apply to all
603 /// extensions. For now, only `:required` exists, but that may change in the
603 /// extensions. For now, only `:required` exists, but that may change in the
604 /// future.
604 /// future.
605 const SUPPORTED_EXTENSIONS: &[&[u8]] =
605 const SUPPORTED_EXTENSIONS: &[&[u8]] =
606 &[b"blackbox", b"share", b"sparse", b"narrow", b"*"];
606 &[b"blackbox", b"share", b"sparse", b"narrow", b"*"];
607
607
608 fn check_extensions(config: &Config) -> Result<(), CommandError> {
608 fn check_extensions(config: &Config) -> Result<(), CommandError> {
609 let enabled: HashSet<&[u8]> = config
609 let enabled: HashSet<&[u8]> = config
610 .get_section_keys(b"extensions")
610 .get_section_keys(b"extensions")
611 .into_iter()
611 .into_iter()
612 .map(|extension| {
612 .map(|extension| {
613 // Ignore extension suboptions. Only `required` exists for now.
613 // Ignore extension suboptions. Only `required` exists for now.
614 // `rhg` either supports an extension or doesn't, so it doesn't
614 // `rhg` either supports an extension or doesn't, so it doesn't
615 // make sense to consider the loading of an extension.
615 // make sense to consider the loading of an extension.
616 extension.split_2(b':').unwrap_or((extension, b"")).0
616 extension.split_2(b':').unwrap_or((extension, b"")).0
617 })
617 })
618 .collect();
618 .collect();
619
619
620 let mut unsupported = enabled;
620 let mut unsupported = enabled;
621 for supported in SUPPORTED_EXTENSIONS {
621 for supported in SUPPORTED_EXTENSIONS {
622 unsupported.remove(supported);
622 unsupported.remove(supported);
623 }
623 }
624
624
625 if let Some(ignored_list) = config.get_list(b"rhg", b"ignored-extensions")
625 if let Some(ignored_list) = config.get_list(b"rhg", b"ignored-extensions")
626 {
626 {
627 for ignored in ignored_list {
627 for ignored in ignored_list {
628 unsupported.remove(ignored.as_slice());
628 unsupported.remove(ignored.as_slice());
629 }
629 }
630 }
630 }
631
631
632 if unsupported.is_empty() {
632 if unsupported.is_empty() {
633 Ok(())
633 Ok(())
634 } else {
634 } else {
635 Err(CommandError::UnsupportedFeature {
635 Err(CommandError::UnsupportedFeature {
636 message: format_bytes!(
636 message: format_bytes!(
637 b"extensions: {} (consider adding them to 'rhg.ignored-extensions' config)",
637 b"extensions: {} (consider adding them to 'rhg.ignored-extensions' config)",
638 join(unsupported, b", ")
638 join(unsupported, b", ")
639 ),
639 ),
640 })
640 })
641 }
641 }
642 }
642 }
643
643
644 fn check_unsupported(
644 fn check_unsupported(
645 config: &Config,
645 config: &Config,
646 repo: Result<&Repo, &NoRepoInCwdError>,
646 repo: Result<&Repo, &NoRepoInCwdError>,
647 ui: &ui::Ui,
647 ui: &ui::Ui,
648 ) -> Result<(), CommandError> {
648 ) -> Result<(), CommandError> {
649 check_extensions(config)?;
649 check_extensions(config)?;
650
650
651 if std::env::var_os("HG_PENDING").is_some() {
651 if std::env::var_os("HG_PENDING").is_some() {
652 // TODO: only if the value is `== repo.working_directory`?
652 // TODO: only if the value is `== repo.working_directory`?
653 // What about relative v.s. absolute paths?
653 // What about relative v.s. absolute paths?
654 Err(CommandError::unsupported("$HG_PENDING"))?
654 Err(CommandError::unsupported("$HG_PENDING"))?
655 }
655 }
656
656
657 if let Ok(repo) = repo {
657 if let Ok(repo) = repo {
658 if repo.has_subrepos()? {
658 if repo.has_subrepos()? {
659 Err(CommandError::unsupported("sub-repositories"))?
659 Err(CommandError::unsupported("sub-repositories"))?
660 }
660 }
661 }
661 }
662
662
663 if config.has_non_empty_section(b"encode") {
663 if config.has_non_empty_section(b"encode") {
664 Err(CommandError::unsupported("[encode] config"))?
664 Err(CommandError::unsupported("[encode] config"))?
665 }
665 }
666
666
667 if config.has_non_empty_section(b"decode") {
667 if config.has_non_empty_section(b"decode") {
668 Err(CommandError::unsupported("[decode] config"))?
668 Err(CommandError::unsupported("[decode] config"))?
669 }
669 }
670
670
671 if let Some(color) = config.get(b"ui", b"color") {
671 if let Some(color) = config.get(b"ui", b"color") {
672 if (color == b"always" || color == b"debug")
672 if (color == b"always" || color == b"debug")
673 && !ui.plain(Some("color"))
673 && !ui.plain(Some("color"))
674 {
674 {
675 Err(CommandError::unsupported("colored output"))?
675 Err(CommandError::unsupported("colored output"))?
676 }
676 }
677 }
677 }
678
678
679 Ok(())
679 Ok(())
680 }
680 }
@@ -1,144 +1,145 b''
1 use format_bytes::format_bytes;
1 use format_bytes::format_bytes;
2 use hg::config::Config;
2 use hg::utils::files::get_bytes_from_os_string;
3 use hg::utils::files::get_bytes_from_os_string;
3 use std::borrow::Cow;
4 use std::borrow::Cow;
4 use std::env;
5 use std::env;
5 use std::io;
6 use std::io;
6 use std::io::{ErrorKind, Write};
7 use std::io::{ErrorKind, Write};
7
8
8 #[derive(Debug)]
9 #[derive(Debug)]
9 pub struct Ui {
10 pub struct Ui {
10 stdout: std::io::Stdout,
11 stdout: std::io::Stdout,
11 stderr: std::io::Stderr,
12 stderr: std::io::Stderr,
12 }
13 }
13
14
14 /// The kind of user interface error
15 /// The kind of user interface error
15 pub enum UiError {
16 pub enum UiError {
16 /// The standard output stream cannot be written to
17 /// The standard output stream cannot be written to
17 StdoutError(io::Error),
18 StdoutError(io::Error),
18 /// The standard error stream cannot be written to
19 /// The standard error stream cannot be written to
19 StderrError(io::Error),
20 StderrError(io::Error),
20 }
21 }
21
22
22 /// The commandline user interface
23 /// The commandline user interface
23 impl Ui {
24 impl Ui {
24 pub fn new() -> Self {
25 pub fn new(_config: &Config) -> Self {
25 Ui {
26 Ui {
26 stdout: std::io::stdout(),
27 stdout: std::io::stdout(),
27 stderr: std::io::stderr(),
28 stderr: std::io::stderr(),
28 }
29 }
29 }
30 }
30
31
31 /// Returns a buffered handle on stdout for faster batch printing
32 /// Returns a buffered handle on stdout for faster batch printing
32 /// operations.
33 /// operations.
33 pub fn stdout_buffer(&self) -> StdoutBuffer<std::io::StdoutLock> {
34 pub fn stdout_buffer(&self) -> StdoutBuffer<std::io::StdoutLock> {
34 StdoutBuffer::new(self.stdout.lock())
35 StdoutBuffer::new(self.stdout.lock())
35 }
36 }
36
37
37 /// Write bytes to stdout
38 /// Write bytes to stdout
38 pub fn write_stdout(&self, bytes: &[u8]) -> Result<(), UiError> {
39 pub fn write_stdout(&self, bytes: &[u8]) -> Result<(), UiError> {
39 let mut stdout = self.stdout.lock();
40 let mut stdout = self.stdout.lock();
40
41
41 stdout.write_all(bytes).or_else(handle_stdout_error)?;
42 stdout.write_all(bytes).or_else(handle_stdout_error)?;
42
43
43 stdout.flush().or_else(handle_stdout_error)
44 stdout.flush().or_else(handle_stdout_error)
44 }
45 }
45
46
46 /// Write bytes to stderr
47 /// Write bytes to stderr
47 pub fn write_stderr(&self, bytes: &[u8]) -> Result<(), UiError> {
48 pub fn write_stderr(&self, bytes: &[u8]) -> Result<(), UiError> {
48 let mut stderr = self.stderr.lock();
49 let mut stderr = self.stderr.lock();
49
50
50 stderr.write_all(bytes).or_else(handle_stderr_error)?;
51 stderr.write_all(bytes).or_else(handle_stderr_error)?;
51
52
52 stderr.flush().or_else(handle_stderr_error)
53 stderr.flush().or_else(handle_stderr_error)
53 }
54 }
54
55
55 /// Return whether plain mode is active.
56 /// Return whether plain mode is active.
56 ///
57 ///
57 /// Plain mode means that all configuration variables which affect
58 /// Plain mode means that all configuration variables which affect
58 /// the behavior and output of Mercurial should be
59 /// the behavior and output of Mercurial should be
59 /// ignored. Additionally, the output should be stable,
60 /// ignored. Additionally, the output should be stable,
60 /// reproducible and suitable for use in scripts or applications.
61 /// reproducible and suitable for use in scripts or applications.
61 ///
62 ///
62 /// The only way to trigger plain mode is by setting either the
63 /// The only way to trigger plain mode is by setting either the
63 /// `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
64 /// `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
64 ///
65 ///
65 /// The return value can either be
66 /// The return value can either be
66 /// - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
67 /// - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
67 /// - False if feature is disabled by default and not included in HGPLAIN
68 /// - False if feature is disabled by default and not included in HGPLAIN
68 /// - True otherwise
69 /// - True otherwise
69 pub fn plain(&self, feature: Option<&str>) -> bool {
70 pub fn plain(&self, feature: Option<&str>) -> bool {
70 plain(feature)
71 plain(feature)
71 }
72 }
72 }
73 }
73
74
74 fn plain(opt_feature: Option<&str>) -> bool {
75 fn plain(opt_feature: Option<&str>) -> bool {
75 if let Some(except) = env::var_os("HGPLAINEXCEPT") {
76 if let Some(except) = env::var_os("HGPLAINEXCEPT") {
76 opt_feature.map_or(true, |feature| {
77 opt_feature.map_or(true, |feature| {
77 get_bytes_from_os_string(except)
78 get_bytes_from_os_string(except)
78 .split(|&byte| byte == b',')
79 .split(|&byte| byte == b',')
79 .all(|exception| exception != feature.as_bytes())
80 .all(|exception| exception != feature.as_bytes())
80 })
81 })
81 } else {
82 } else {
82 env::var_os("HGPLAIN").is_some()
83 env::var_os("HGPLAIN").is_some()
83 }
84 }
84 }
85 }
85
86
86 /// A buffered stdout writer for faster batch printing operations.
87 /// A buffered stdout writer for faster batch printing operations.
87 pub struct StdoutBuffer<W: Write> {
88 pub struct StdoutBuffer<W: Write> {
88 buf: io::BufWriter<W>,
89 buf: io::BufWriter<W>,
89 }
90 }
90
91
91 impl<W: Write> StdoutBuffer<W> {
92 impl<W: Write> StdoutBuffer<W> {
92 pub fn new(writer: W) -> Self {
93 pub fn new(writer: W) -> Self {
93 let buf = io::BufWriter::new(writer);
94 let buf = io::BufWriter::new(writer);
94 Self { buf }
95 Self { buf }
95 }
96 }
96
97
97 /// Write bytes to stdout buffer
98 /// Write bytes to stdout buffer
98 pub fn write_all(&mut self, bytes: &[u8]) -> Result<(), UiError> {
99 pub fn write_all(&mut self, bytes: &[u8]) -> Result<(), UiError> {
99 self.buf.write_all(bytes).or_else(handle_stdout_error)
100 self.buf.write_all(bytes).or_else(handle_stdout_error)
100 }
101 }
101
102
102 /// Flush bytes to stdout
103 /// Flush bytes to stdout
103 pub fn flush(&mut self) -> Result<(), UiError> {
104 pub fn flush(&mut self) -> Result<(), UiError> {
104 self.buf.flush().or_else(handle_stdout_error)
105 self.buf.flush().or_else(handle_stdout_error)
105 }
106 }
106 }
107 }
107
108
108 /// Sometimes writing to stdout is not possible, try writing to stderr to
109 /// Sometimes writing to stdout is not possible, try writing to stderr to
109 /// signal that failure, otherwise just bail.
110 /// signal that failure, otherwise just bail.
110 fn handle_stdout_error(error: io::Error) -> Result<(), UiError> {
111 fn handle_stdout_error(error: io::Error) -> Result<(), UiError> {
111 if let ErrorKind::BrokenPipe = error.kind() {
112 if let ErrorKind::BrokenPipe = error.kind() {
112 // This makes `| head` work for example
113 // This makes `| head` work for example
113 return Ok(());
114 return Ok(());
114 }
115 }
115 let mut stderr = io::stderr();
116 let mut stderr = io::stderr();
116
117
117 stderr
118 stderr
118 .write_all(&format_bytes!(
119 .write_all(&format_bytes!(
119 b"abort: {}\n",
120 b"abort: {}\n",
120 error.to_string().as_bytes()
121 error.to_string().as_bytes()
121 ))
122 ))
122 .map_err(UiError::StderrError)?;
123 .map_err(UiError::StderrError)?;
123
124
124 stderr.flush().map_err(UiError::StderrError)?;
125 stderr.flush().map_err(UiError::StderrError)?;
125
126
126 Err(UiError::StdoutError(error))
127 Err(UiError::StdoutError(error))
127 }
128 }
128
129
129 /// Sometimes writing to stderr is not possible.
130 /// Sometimes writing to stderr is not possible.
130 fn handle_stderr_error(error: io::Error) -> Result<(), UiError> {
131 fn handle_stderr_error(error: io::Error) -> Result<(), UiError> {
131 // A broken pipe should not result in a error
132 // A broken pipe should not result in a error
132 // like with `| head` for example
133 // like with `| head` for example
133 if let ErrorKind::BrokenPipe = error.kind() {
134 if let ErrorKind::BrokenPipe = error.kind() {
134 return Ok(());
135 return Ok(());
135 }
136 }
136 Err(UiError::StdoutError(error))
137 Err(UiError::StdoutError(error))
137 }
138 }
138
139
139 /// Encode rust strings according to the user system.
140 /// Encode rust strings according to the user system.
140 pub fn utf8_to_local(s: &str) -> Cow<[u8]> {
141 pub fn utf8_to_local(s: &str) -> Cow<[u8]> {
141 // TODO encode for the user's system //
142 // TODO encode for the user's system //
142 let bytes = s.as_bytes();
143 let bytes = s.as_bytes();
143 Cow::Borrowed(bytes)
144 Cow::Borrowed(bytes)
144 }
145 }
General Comments 0
You need to be logged in to leave comments. Login now