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