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