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