##// END OF EJS Templates
rhg: tweak rhg fallback code structure...
Arseniy Alekseyev -
r51702:53d77f96 default
parent child Browse files
Show More
@@ -1,833 +1,837 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::{command, Arg, ArgMatches};
4 use clap::{command, Arg, ArgMatches};
5 use format_bytes::{format_bytes, join};
5 use format_bytes::{format_bytes, join};
6 use hg::config::{Config, ConfigSource, PlainInfo};
6 use hg::config::{Config, ConfigSource, PlainInfo};
7 use hg::repo::{Repo, RepoError};
7 use hg::repo::{Repo, RepoError};
8 use hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes};
8 use hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes};
9 use hg::utils::SliceExt;
9 use hg::utils::SliceExt;
10 use hg::{exit_codes, requirements};
10 use hg::{exit_codes, requirements};
11 use std::borrow::Cow;
11 use std::borrow::Cow;
12 use std::collections::HashSet;
12 use std::collections::HashSet;
13 use std::ffi::OsString;
13 use std::ffi::OsString;
14 use std::os::unix::prelude::CommandExt;
14 use std::os::unix::prelude::CommandExt;
15 use std::path::PathBuf;
15 use std::path::PathBuf;
16 use std::process::Command;
16 use std::process::Command;
17
17
18 mod blackbox;
18 mod blackbox;
19 mod color;
19 mod color;
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 argv: Vec<OsString>,
27 argv: Vec<OsString>,
28 process_start_time: &blackbox::ProcessStartTime,
28 process_start_time: &blackbox::ProcessStartTime,
29 ui: &ui::Ui,
29 ui: &ui::Ui,
30 repo: Result<&Repo, &NoRepoInCwdError>,
30 repo: Result<&Repo, &NoRepoInCwdError>,
31 config: &Config,
31 config: &Config,
32 ) -> Result<(), CommandError> {
32 ) -> Result<(), CommandError> {
33 check_unsupported(config, repo)?;
33 check_unsupported(config, repo)?;
34
34
35 let app = command!()
35 let app = command!()
36 .subcommand_required(true)
36 .subcommand_required(true)
37 .arg(
37 .arg(
38 Arg::new("repository")
38 Arg::new("repository")
39 .help("repository root directory")
39 .help("repository root directory")
40 .short('R')
40 .short('R')
41 .value_name("REPO")
41 .value_name("REPO")
42 // Both ok: `hg -R ./foo log` or `hg log -R ./foo`
42 // Both ok: `hg -R ./foo log` or `hg log -R ./foo`
43 .global(true),
43 .global(true),
44 )
44 )
45 .arg(
45 .arg(
46 Arg::new("config")
46 Arg::new("config")
47 .help("set/override config option (use 'section.name=value')")
47 .help("set/override config option (use 'section.name=value')")
48 .value_name("CONFIG")
48 .value_name("CONFIG")
49 .global(true)
49 .global(true)
50 .long("config")
50 .long("config")
51 // Ok: `--config section.key1=val --config section.key2=val2`
51 // Ok: `--config section.key1=val --config section.key2=val2`
52 // Not ok: `--config section.key1=val section.key2=val2`
52 // Not ok: `--config section.key1=val section.key2=val2`
53 .action(clap::ArgAction::Append),
53 .action(clap::ArgAction::Append),
54 )
54 )
55 .arg(
55 .arg(
56 Arg::new("cwd")
56 Arg::new("cwd")
57 .help("change working directory")
57 .help("change working directory")
58 .value_name("DIR")
58 .value_name("DIR")
59 .long("cwd")
59 .long("cwd")
60 .global(true),
60 .global(true),
61 )
61 )
62 .arg(
62 .arg(
63 Arg::new("color")
63 Arg::new("color")
64 .help("when to colorize (boolean, always, auto, never, or debug)")
64 .help("when to colorize (boolean, always, auto, never, or debug)")
65 .value_name("TYPE")
65 .value_name("TYPE")
66 .long("color")
66 .long("color")
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.try_get_matches_from(argv.iter())?;
72 let matches = app.try_get_matches_from(argv.iter())?;
73
73
74 let (subcommand_name, subcommand_args) =
74 let (subcommand_name, subcommand_args) =
75 matches.subcommand().expect("subcommand required");
75 matches.subcommand().expect("subcommand required");
76
76
77 // Mercurial allows users to define "defaults" for commands, fallback
77 // Mercurial allows users to define "defaults" for commands, fallback
78 // if a default is detected for the current command
78 // if a default is detected for the current command
79 let defaults = config.get_str(b"defaults", subcommand_name.as_bytes());
79 let defaults = config.get_str(b"defaults", subcommand_name.as_bytes());
80 if defaults?.is_some() {
80 if defaults?.is_some() {
81 let msg = "`defaults` config set";
81 let msg = "`defaults` config set";
82 return Err(CommandError::unsupported(msg));
82 return Err(CommandError::unsupported(msg));
83 }
83 }
84
84
85 for prefix in ["pre", "post", "fail"].iter() {
85 for prefix in ["pre", "post", "fail"].iter() {
86 // Mercurial allows users to define generic hooks for commands,
86 // Mercurial allows users to define generic hooks for commands,
87 // fallback if any are detected
87 // fallback if any are detected
88 let item = format!("{}-{}", prefix, subcommand_name);
88 let item = format!("{}-{}", prefix, subcommand_name);
89 let hook_for_command =
89 let hook_for_command =
90 config.get_str_no_default(b"hooks", item.as_bytes())?;
90 config.get_str_no_default(b"hooks", item.as_bytes())?;
91 if hook_for_command.is_some() {
91 if hook_for_command.is_some() {
92 let msg = format!("{}-{} hook defined", prefix, subcommand_name);
92 let msg = format!("{}-{} hook defined", prefix, subcommand_name);
93 return Err(CommandError::unsupported(msg));
93 return Err(CommandError::unsupported(msg));
94 }
94 }
95 }
95 }
96 let run = subcommand_run_fn(subcommand_name)
96 let run = subcommand_run_fn(subcommand_name)
97 .expect("unknown subcommand name from clap despite Command::subcommand_required");
97 .expect("unknown subcommand name from clap despite Command::subcommand_required");
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(argv.iter());
117 blackbox.log_command_start(argv.iter());
118 let result = run(&invocation);
118 let result = run(&invocation);
119 blackbox.log_command_end(
119 blackbox.log_command_end(
120 argv.iter(),
120 argv.iter(),
121 exit_code(
121 exit_code(
122 &result,
122 &result,
123 // TODO: show a warning or combine with original error if
123 // TODO: show a warning or combine with original error if
124 // `get_bool` returns an error
124 // `get_bool` returns an error
125 config
125 config
126 .get_bool(b"ui", b"detailed-exit-code")
126 .get_bool(b"ui", b"detailed-exit-code")
127 .unwrap_or(false),
127 .unwrap_or(false),
128 ),
128 ),
129 );
129 );
130 result
130 result
131 } else {
131 } else {
132 run(&invocation)
132 run(&invocation)
133 }
133 }
134 }
134 }
135
135
136 fn rhg_main(argv: Vec<OsString>) -> ! {
136 fn rhg_main(argv: Vec<OsString>) -> ! {
137 // Run this first, before we find out if the blackbox extension is even
137 // Run this first, before we find out if the blackbox extension is even
138 // enabled, in order to include everything in-between in the duration
138 // enabled, in order to include everything in-between in the duration
139 // measurements. Reading config files can be slow if they’re on NFS.
139 // measurements. Reading config files can be slow if they’re on NFS.
140 let process_start_time = blackbox::ProcessStartTime::now();
140 let process_start_time = blackbox::ProcessStartTime::now();
141
141
142 env_logger::init();
142 env_logger::init();
143
143
144 // Make sure nothing in a future version of `rhg` sets the global
144 // Make sure nothing in a future version of `rhg` sets the global
145 // threadpool before we can cap default threads. (This is also called
145 // threadpool before we can cap default threads. (This is also called
146 // in core because Python uses the same code path, we're adding a
146 // in core because Python uses the same code path, we're adding a
147 // redundant check.)
147 // redundant check.)
148 hg::utils::cap_default_rayon_threads()
148 hg::utils::cap_default_rayon_threads()
149 .expect("Rayon threadpool already initialized");
149 .expect("Rayon threadpool already initialized");
150
150
151 let early_args = EarlyArgs::parse(&argv);
151 let early_args = EarlyArgs::parse(&argv);
152
152
153 let initial_current_dir = early_args.cwd.map(|cwd| {
153 let initial_current_dir = early_args.cwd.map(|cwd| {
154 let cwd = get_path_from_bytes(&cwd);
154 let cwd = get_path_from_bytes(&cwd);
155 std::env::current_dir()
155 std::env::current_dir()
156 .and_then(|initial| {
156 .and_then(|initial| {
157 std::env::set_current_dir(cwd)?;
157 std::env::set_current_dir(cwd)?;
158 Ok(initial)
158 Ok(initial)
159 })
159 })
160 .unwrap_or_else(|error| {
160 .unwrap_or_else(|error| {
161 exit(
161 exit(
162 &argv,
162 &argv,
163 &None,
163 &None,
164 &Ui::new_infallible(&Config::empty()),
164 &Ui::new_infallible(&Config::empty()),
165 OnUnsupported::Abort,
165 OnUnsupported::Abort,
166 Err(CommandError::abort(format!(
166 Err(CommandError::abort(format!(
167 "abort: {}: '{}'",
167 "abort: {}: '{}'",
168 error,
168 error,
169 cwd.display()
169 cwd.display()
170 ))),
170 ))),
171 false,
171 false,
172 )
172 )
173 })
173 })
174 });
174 });
175
175
176 let mut non_repo_config =
176 let mut non_repo_config =
177 Config::load_non_repo().unwrap_or_else(|error| {
177 Config::load_non_repo().unwrap_or_else(|error| {
178 // Normally this is decided based on config, but we don’t have that
178 // Normally this is decided based on config, but we don’t have that
179 // available. As of this writing config loading never returns an
179 // available. As of this writing config loading never returns an
180 // "unsupported" error but that is not enforced by the type system.
180 // "unsupported" error but that is not enforced by the type system.
181 let on_unsupported = OnUnsupported::Abort;
181 let on_unsupported = OnUnsupported::Abort;
182
182
183 exit(
183 exit(
184 &argv,
184 &argv,
185 &initial_current_dir,
185 &initial_current_dir,
186 &Ui::new_infallible(&Config::empty()),
186 &Ui::new_infallible(&Config::empty()),
187 on_unsupported,
187 on_unsupported,
188 Err(error.into()),
188 Err(error.into()),
189 false,
189 false,
190 )
190 )
191 });
191 });
192
192
193 non_repo_config
193 non_repo_config
194 .load_cli_args(early_args.config, early_args.color)
194 .load_cli_args(early_args.config, early_args.color)
195 .unwrap_or_else(|error| {
195 .unwrap_or_else(|error| {
196 exit(
196 exit(
197 &argv,
197 &argv,
198 &initial_current_dir,
198 &initial_current_dir,
199 &Ui::new_infallible(&non_repo_config),
199 &Ui::new_infallible(&non_repo_config),
200 OnUnsupported::from_config(&non_repo_config),
200 OnUnsupported::from_config(&non_repo_config),
201 Err(error.into()),
201 Err(error.into()),
202 non_repo_config
202 non_repo_config
203 .get_bool(b"ui", b"detailed-exit-code")
203 .get_bool(b"ui", b"detailed-exit-code")
204 .unwrap_or(false),
204 .unwrap_or(false),
205 )
205 )
206 });
206 });
207
207
208 if let Some(repo_path_bytes) = &early_args.repo {
208 if let Some(repo_path_bytes) = &early_args.repo {
209 lazy_static::lazy_static! {
209 lazy_static::lazy_static! {
210 static ref SCHEME_RE: regex::bytes::Regex =
210 static ref SCHEME_RE: regex::bytes::Regex =
211 // Same as `_matchscheme` in `mercurial/util.py`
211 // Same as `_matchscheme` in `mercurial/util.py`
212 regex::bytes::Regex::new("^[a-zA-Z0-9+.\\-]+:").unwrap();
212 regex::bytes::Regex::new("^[a-zA-Z0-9+.\\-]+:").unwrap();
213 }
213 }
214 if SCHEME_RE.is_match(repo_path_bytes) {
214 if SCHEME_RE.is_match(repo_path_bytes) {
215 exit(
215 exit(
216 &argv,
216 &argv,
217 &initial_current_dir,
217 &initial_current_dir,
218 &Ui::new_infallible(&non_repo_config),
218 &Ui::new_infallible(&non_repo_config),
219 OnUnsupported::from_config(&non_repo_config),
219 OnUnsupported::from_config(&non_repo_config),
220 Err(CommandError::UnsupportedFeature {
220 Err(CommandError::UnsupportedFeature {
221 message: format_bytes!(
221 message: format_bytes!(
222 b"URL-like --repository {}",
222 b"URL-like --repository {}",
223 repo_path_bytes
223 repo_path_bytes
224 ),
224 ),
225 }),
225 }),
226 // TODO: show a warning or combine with original error if
226 // TODO: show a warning or combine with original error if
227 // `get_bool` returns an error
227 // `get_bool` returns an error
228 non_repo_config
228 non_repo_config
229 .get_bool(b"ui", b"detailed-exit-code")
229 .get_bool(b"ui", b"detailed-exit-code")
230 .unwrap_or(false),
230 .unwrap_or(false),
231 )
231 )
232 }
232 }
233 }
233 }
234 let repo_arg = early_args.repo.unwrap_or_default();
234 let repo_arg = early_args.repo.unwrap_or_default();
235 let repo_path: Option<PathBuf> = {
235 let repo_path: Option<PathBuf> = {
236 if repo_arg.is_empty() {
236 if repo_arg.is_empty() {
237 None
237 None
238 } else {
238 } else {
239 let local_config = {
239 let local_config = {
240 if std::env::var_os("HGRCSKIPREPO").is_none() {
240 if std::env::var_os("HGRCSKIPREPO").is_none() {
241 // TODO: handle errors from find_repo_root
241 // TODO: handle errors from find_repo_root
242 if let Ok(current_dir_path) = Repo::find_repo_root() {
242 if let Ok(current_dir_path) = Repo::find_repo_root() {
243 let config_files = vec![
243 let config_files = vec![
244 ConfigSource::AbsPath(
244 ConfigSource::AbsPath(
245 current_dir_path.join(".hg/hgrc"),
245 current_dir_path.join(".hg/hgrc"),
246 ),
246 ),
247 ConfigSource::AbsPath(
247 ConfigSource::AbsPath(
248 current_dir_path.join(".hg/hgrc-not-shared"),
248 current_dir_path.join(".hg/hgrc-not-shared"),
249 ),
249 ),
250 ];
250 ];
251 // TODO: handle errors from
251 // TODO: handle errors from
252 // `load_from_explicit_sources`
252 // `load_from_explicit_sources`
253 Config::load_from_explicit_sources(config_files).ok()
253 Config::load_from_explicit_sources(config_files).ok()
254 } else {
254 } else {
255 None
255 None
256 }
256 }
257 } else {
257 } else {
258 None
258 None
259 }
259 }
260 };
260 };
261
261
262 let non_repo_config_val = {
262 let non_repo_config_val = {
263 let non_repo_val = non_repo_config.get(b"paths", &repo_arg);
263 let non_repo_val = non_repo_config.get(b"paths", &repo_arg);
264 match &non_repo_val {
264 match &non_repo_val {
265 Some(val) if !val.is_empty() => home::home_dir()
265 Some(val) if !val.is_empty() => home::home_dir()
266 .unwrap_or_else(|| PathBuf::from("~"))
266 .unwrap_or_else(|| PathBuf::from("~"))
267 .join(get_path_from_bytes(val))
267 .join(get_path_from_bytes(val))
268 .canonicalize()
268 .canonicalize()
269 // TODO: handle error and make it similar to python
269 // TODO: handle error and make it similar to python
270 // implementation maybe?
270 // implementation maybe?
271 .ok(),
271 .ok(),
272 _ => None,
272 _ => None,
273 }
273 }
274 };
274 };
275
275
276 let config_val = match &local_config {
276 let config_val = match &local_config {
277 None => non_repo_config_val,
277 None => non_repo_config_val,
278 Some(val) => {
278 Some(val) => {
279 let local_config_val = val.get(b"paths", &repo_arg);
279 let local_config_val = val.get(b"paths", &repo_arg);
280 match &local_config_val {
280 match &local_config_val {
281 Some(val) if !val.is_empty() => {
281 Some(val) if !val.is_empty() => {
282 // presence of a local_config assures that
282 // presence of a local_config assures that
283 // current_dir
283 // current_dir
284 // wont result in an Error
284 // wont result in an Error
285 let canpath = hg::utils::current_dir()
285 let canpath = hg::utils::current_dir()
286 .unwrap()
286 .unwrap()
287 .join(get_path_from_bytes(val))
287 .join(get_path_from_bytes(val))
288 .canonicalize();
288 .canonicalize();
289 canpath.ok().or(non_repo_config_val)
289 canpath.ok().or(non_repo_config_val)
290 }
290 }
291 _ => non_repo_config_val,
291 _ => non_repo_config_val,
292 }
292 }
293 }
293 }
294 };
294 };
295 config_val
295 config_val
296 .or_else(|| Some(get_path_from_bytes(&repo_arg).to_path_buf()))
296 .or_else(|| Some(get_path_from_bytes(&repo_arg).to_path_buf()))
297 }
297 }
298 };
298 };
299
299
300 let simple_exit =
300 let simple_exit =
301 |ui: &Ui, config: &Config, result: Result<(), CommandError>| -> ! {
301 |ui: &Ui, config: &Config, result: Result<(), CommandError>| -> ! {
302 exit(
302 exit(
303 &argv,
303 &argv,
304 &initial_current_dir,
304 &initial_current_dir,
305 ui,
305 ui,
306 OnUnsupported::from_config(config),
306 OnUnsupported::from_config(config),
307 result,
307 result,
308 // TODO: show a warning or combine with original error if
308 // TODO: show a warning or combine with original error if
309 // `get_bool` returns an error
309 // `get_bool` returns an error
310 non_repo_config
310 non_repo_config
311 .get_bool(b"ui", b"detailed-exit-code")
311 .get_bool(b"ui", b"detailed-exit-code")
312 .unwrap_or(false),
312 .unwrap_or(false),
313 )
313 )
314 };
314 };
315 let early_exit = |config: &Config, error: CommandError| -> ! {
315 let early_exit = |config: &Config, error: CommandError| -> ! {
316 simple_exit(&Ui::new_infallible(config), config, Err(error))
316 simple_exit(&Ui::new_infallible(config), config, Err(error))
317 };
317 };
318 let repo_result = match Repo::find(&non_repo_config, repo_path.to_owned())
318 let repo_result = match Repo::find(&non_repo_config, repo_path.to_owned())
319 {
319 {
320 Ok(repo) => Ok(repo),
320 Ok(repo) => Ok(repo),
321 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
321 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
322 // Not finding a repo is not fatal yet, if `-R` was not given
322 // Not finding a repo is not fatal yet, if `-R` was not given
323 Err(NoRepoInCwdError { cwd: at })
323 Err(NoRepoInCwdError { cwd: at })
324 }
324 }
325 Err(error) => early_exit(&non_repo_config, error.into()),
325 Err(error) => early_exit(&non_repo_config, error.into()),
326 };
326 };
327
327
328 let config = if let Ok(repo) = &repo_result {
328 let config = if let Ok(repo) = &repo_result {
329 repo.config()
329 repo.config()
330 } else {
330 } else {
331 &non_repo_config
331 &non_repo_config
332 };
332 };
333
333
334 let mut config_cow = Cow::Borrowed(config);
334 let mut config_cow = Cow::Borrowed(config);
335 config_cow.to_mut().apply_plain(PlainInfo::from_env());
335 config_cow.to_mut().apply_plain(PlainInfo::from_env());
336 if !ui::plain(Some("tweakdefaults"))
336 if !ui::plain(Some("tweakdefaults"))
337 && config_cow
337 && config_cow
338 .as_ref()
338 .as_ref()
339 .get_bool(b"ui", b"tweakdefaults")
339 .get_bool(b"ui", b"tweakdefaults")
340 .unwrap_or_else(|error| early_exit(config, error.into()))
340 .unwrap_or_else(|error| early_exit(config, error.into()))
341 {
341 {
342 config_cow.to_mut().tweakdefaults()
342 config_cow.to_mut().tweakdefaults()
343 };
343 };
344 let config = config_cow.as_ref();
344 let config = config_cow.as_ref();
345 let ui = Ui::new(config)
345 let ui = Ui::new(config)
346 .unwrap_or_else(|error| early_exit(config, error.into()));
346 .unwrap_or_else(|error| early_exit(config, error.into()));
347
347
348 if let Ok(true) = config.get_bool(b"rhg", b"fallback-immediately") {
348 if let Ok(true) = config.get_bool(b"rhg", b"fallback-immediately") {
349 exit(
349 exit(
350 &argv,
350 &argv,
351 &initial_current_dir,
351 &initial_current_dir,
352 &ui,
352 &ui,
353 OnUnsupported::Fallback {
353 OnUnsupported::fallback(config),
354 executable: config
355 .get(b"rhg", b"fallback-executable")
356 .map(ToOwned::to_owned),
357 },
358 Err(CommandError::unsupported(
354 Err(CommandError::unsupported(
359 "`rhg.fallback-immediately is true`",
355 "`rhg.fallback-immediately is true`",
360 )),
356 )),
361 false,
357 false,
362 )
358 )
363 }
359 }
364
360
365 let result = main_with_result(
361 let result = main_with_result(
366 argv.iter().map(|s| s.to_owned()).collect(),
362 argv.iter().map(|s| s.to_owned()).collect(),
367 &process_start_time,
363 &process_start_time,
368 &ui,
364 &ui,
369 repo_result.as_ref(),
365 repo_result.as_ref(),
370 config,
366 config,
371 );
367 );
372 simple_exit(&ui, config, result)
368 simple_exit(&ui, config, result)
373 }
369 }
374
370
375 fn main() -> ! {
371 fn main() -> ! {
376 rhg_main(std::env::args_os().collect())
372 rhg_main(std::env::args_os().collect())
377 }
373 }
378
374
379 fn exit_code(
375 fn exit_code(
380 result: &Result<(), CommandError>,
376 result: &Result<(), CommandError>,
381 use_detailed_exit_code: bool,
377 use_detailed_exit_code: bool,
382 ) -> i32 {
378 ) -> i32 {
383 match result {
379 match result {
384 Ok(()) => exit_codes::OK,
380 Ok(()) => exit_codes::OK,
385 Err(CommandError::Abort {
381 Err(CommandError::Abort {
386 detailed_exit_code, ..
382 detailed_exit_code, ..
387 }) => {
383 }) => {
388 if use_detailed_exit_code {
384 if use_detailed_exit_code {
389 *detailed_exit_code
385 *detailed_exit_code
390 } else {
386 } else {
391 exit_codes::ABORT
387 exit_codes::ABORT
392 }
388 }
393 }
389 }
394 Err(CommandError::Unsuccessful) => exit_codes::UNSUCCESSFUL,
390 Err(CommandError::Unsuccessful) => exit_codes::UNSUCCESSFUL,
395 // Exit with a specific code and no error message to let a potential
391 // Exit with a specific code and no error message to let a potential
396 // wrapper script fallback to Python-based Mercurial.
392 // wrapper script fallback to Python-based Mercurial.
397 Err(CommandError::UnsupportedFeature { .. }) => {
393 Err(CommandError::UnsupportedFeature { .. }) => {
398 exit_codes::UNIMPLEMENTED
394 exit_codes::UNIMPLEMENTED
399 }
395 }
400 Err(CommandError::InvalidFallback { .. }) => {
396 Err(CommandError::InvalidFallback { .. }) => {
401 exit_codes::INVALID_FALLBACK
397 exit_codes::INVALID_FALLBACK
402 }
398 }
403 }
399 }
404 }
400 }
405
401
406 fn exit<'a>(
402 fn exit<'a>(
407 original_args: &'a [OsString],
403 original_args: &'a [OsString],
408 initial_current_dir: &Option<PathBuf>,
404 initial_current_dir: &Option<PathBuf>,
409 ui: &Ui,
405 ui: &Ui,
410 mut on_unsupported: OnUnsupported,
406 mut on_unsupported: OnUnsupported,
411 result: Result<(), CommandError>,
407 result: Result<(), CommandError>,
412 use_detailed_exit_code: bool,
408 use_detailed_exit_code: bool,
413 ) -> ! {
409 ) -> ! {
414 if let (
410 if let (
415 OnUnsupported::Fallback { executable },
411 OnUnsupported::Fallback { executable },
416 Err(CommandError::UnsupportedFeature { message }),
412 Err(CommandError::UnsupportedFeature { message }),
417 ) = (&on_unsupported, &result)
413 ) = (&on_unsupported, &result)
418 {
414 {
419 let mut args = original_args.iter();
415 let mut args = original_args.iter();
420 let executable = match executable {
416 let executable = match executable {
421 None => {
417 None => {
422 exit_no_fallback(
418 exit_no_fallback(
423 ui,
419 ui,
424 OnUnsupported::Abort,
420 OnUnsupported::Abort,
425 Err(CommandError::abort(
421 Err(CommandError::abort(
426 "abort: 'rhg.on-unsupported=fallback' without \
422 "abort: 'rhg.on-unsupported=fallback' without \
427 'rhg.fallback-executable' set.",
423 'rhg.fallback-executable' set.",
428 )),
424 )),
429 false,
425 false,
430 );
426 );
431 }
427 }
432 Some(executable) => executable,
428 Some(executable) => executable,
433 };
429 };
434 let executable_path = get_path_from_bytes(executable);
430 let executable_path = get_path_from_bytes(executable);
435 let this_executable = args.next().expect("exepcted argv[0] to exist");
431 let this_executable = args.next().expect("exepcted argv[0] to exist");
436 if executable_path == *this_executable {
432 if executable_path == *this_executable {
437 // Avoid spawning infinitely many processes until resource
433 // Avoid spawning infinitely many processes until resource
438 // exhaustion.
434 // exhaustion.
439 let _ = ui.write_stderr(&format_bytes!(
435 let _ = ui.write_stderr(&format_bytes!(
440 b"Blocking recursive fallback. The 'rhg.fallback-executable = {}' config \
436 b"Blocking recursive fallback. The 'rhg.fallback-executable = {}' config \
441 points to `rhg` itself.\n",
437 points to `rhg` itself.\n",
442 executable
438 executable
443 ));
439 ));
444 on_unsupported = OnUnsupported::Abort
440 on_unsupported = OnUnsupported::Abort
445 } else {
441 } else {
446 log::debug!("falling back (see trace-level log)");
442 log::debug!("falling back (see trace-level log)");
447 log::trace!("{}", local_to_utf8(message));
443 log::trace!("{}", local_to_utf8(message));
448 if let Err(err) = which::which(executable_path) {
444 if let Err(err) = which::which(executable_path) {
449 exit_no_fallback(
445 exit_no_fallback(
450 ui,
446 ui,
451 OnUnsupported::Abort,
447 OnUnsupported::Abort,
452 Err(CommandError::InvalidFallback {
448 Err(CommandError::InvalidFallback {
453 path: executable.to_owned(),
449 path: executable.to_owned(),
454 err: err.to_string(),
450 err: err.to_string(),
455 }),
451 }),
456 use_detailed_exit_code,
452 use_detailed_exit_code,
457 )
453 )
458 }
454 }
459 // `args` is now `argv[1..]` since we’ve already consumed
455 // `args` is now `argv[1..]` since we’ve already consumed
460 // `argv[0]`
456 // `argv[0]`
461 let mut command = Command::new(executable_path);
457 let mut command = Command::new(executable_path);
462 command.args(args);
458 command.args(args);
463 if let Some(initial) = initial_current_dir {
459 if let Some(initial) = initial_current_dir {
464 command.current_dir(initial);
460 command.current_dir(initial);
465 }
461 }
466 // We don't use subprocess because proper signal handling is harder
462 // We don't use subprocess because proper signal handling is harder
467 // and we don't want to keep `rhg` around after a fallback anyway.
463 // and we don't want to keep `rhg` around after a fallback anyway.
468 // For example, if `rhg` is run in the background and falls back to
464 // For example, if `rhg` is run in the background and falls back to
469 // `hg` which, in turn, waits for a signal, we'll get stuck if
465 // `hg` which, in turn, waits for a signal, we'll get stuck if
470 // we're doing plain subprocess.
466 // we're doing plain subprocess.
471 //
467 //
472 // If `exec` returns, we can only assume our process is very broken
468 // If `exec` returns, we can only assume our process is very broken
473 // (see its documentation), so only try to forward the error code
469 // (see its documentation), so only try to forward the error code
474 // when exiting.
470 // when exiting.
475 let err = command.exec();
471 let err = command.exec();
476 std::process::exit(
472 std::process::exit(
477 err.raw_os_error().unwrap_or(exit_codes::ABORT),
473 err.raw_os_error().unwrap_or(exit_codes::ABORT),
478 );
474 );
479 }
475 }
480 }
476 }
481 exit_no_fallback(ui, on_unsupported, result, use_detailed_exit_code)
477 exit_no_fallback(ui, on_unsupported, result, use_detailed_exit_code)
482 }
478 }
483
479
484 fn exit_no_fallback(
480 fn exit_no_fallback(
485 ui: &Ui,
481 ui: &Ui,
486 on_unsupported: OnUnsupported,
482 on_unsupported: OnUnsupported,
487 result: Result<(), CommandError>,
483 result: Result<(), CommandError>,
488 use_detailed_exit_code: bool,
484 use_detailed_exit_code: bool,
489 ) -> ! {
485 ) -> ! {
490 match &result {
486 match &result {
491 Ok(_) => {}
487 Ok(_) => {}
492 Err(CommandError::Unsuccessful) => {}
488 Err(CommandError::Unsuccessful) => {}
493 Err(CommandError::Abort { message, hint, .. }) => {
489 Err(CommandError::Abort { message, hint, .. }) => {
494 // Ignore errors when writing to stderr, we’re already exiting
490 // Ignore errors when writing to stderr, we’re already exiting
495 // with failure code so there’s not much more we can do.
491 // with failure code so there’s not much more we can do.
496 if !message.is_empty() {
492 if !message.is_empty() {
497 let _ = ui.write_stderr(&format_bytes!(b"{}\n", message));
493 let _ = ui.write_stderr(&format_bytes!(b"{}\n", message));
498 }
494 }
499 if let Some(hint) = hint {
495 if let Some(hint) = hint {
500 let _ = ui.write_stderr(&format_bytes!(b"({})\n", hint));
496 let _ = ui.write_stderr(&format_bytes!(b"({})\n", hint));
501 }
497 }
502 }
498 }
503 Err(CommandError::UnsupportedFeature { message }) => {
499 Err(CommandError::UnsupportedFeature { message }) => {
504 match on_unsupported {
500 match on_unsupported {
505 OnUnsupported::Abort => {
501 OnUnsupported::Abort => {
506 let _ = ui.write_stderr(&format_bytes!(
502 let _ = ui.write_stderr(&format_bytes!(
507 b"unsupported feature: {}\n",
503 b"unsupported feature: {}\n",
508 message
504 message
509 ));
505 ));
510 }
506 }
511 OnUnsupported::AbortSilent => {}
507 OnUnsupported::AbortSilent => {}
512 OnUnsupported::Fallback { .. } => unreachable!(),
508 OnUnsupported::Fallback { .. } => unreachable!(),
513 }
509 }
514 }
510 }
515 Err(CommandError::InvalidFallback { path, err }) => {
511 Err(CommandError::InvalidFallback { path, err }) => {
516 let _ = ui.write_stderr(&format_bytes!(
512 let _ = ui.write_stderr(&format_bytes!(
517 b"abort: invalid fallback '{}': {}\n",
513 b"abort: invalid fallback '{}': {}\n",
518 path,
514 path,
519 err.as_bytes(),
515 err.as_bytes(),
520 ));
516 ));
521 }
517 }
522 }
518 }
523 std::process::exit(exit_code(&result, use_detailed_exit_code))
519 std::process::exit(exit_code(&result, use_detailed_exit_code))
524 }
520 }
525
521
526 macro_rules! subcommands {
522 macro_rules! subcommands {
527 ($( $command: ident )+) => {
523 ($( $command: ident )+) => {
528 mod commands {
524 mod commands {
529 $(
525 $(
530 pub mod $command;
526 pub mod $command;
531 )+
527 )+
532 }
528 }
533
529
534 fn add_subcommand_args(app: clap::Command) -> clap::Command {
530 fn add_subcommand_args(app: clap::Command) -> clap::Command {
535 app
531 app
536 $(
532 $(
537 .subcommand(commands::$command::args())
533 .subcommand(commands::$command::args())
538 )+
534 )+
539 }
535 }
540
536
541 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
537 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
542
538
543 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
539 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
544 match name {
540 match name {
545 $(
541 $(
546 stringify!($command) => Some(commands::$command::run),
542 stringify!($command) => Some(commands::$command::run),
547 )+
543 )+
548 _ => None,
544 _ => None,
549 }
545 }
550 }
546 }
551 };
547 };
552 }
548 }
553
549
554 subcommands! {
550 subcommands! {
555 cat
551 cat
556 debugdata
552 debugdata
557 debugrequirements
553 debugrequirements
558 debugignorerhg
554 debugignorerhg
559 debugrhgsparse
555 debugrhgsparse
560 files
556 files
561 root
557 root
562 config
558 config
563 status
559 status
564 }
560 }
565
561
566 pub struct CliInvocation<'a> {
562 pub struct CliInvocation<'a> {
567 ui: &'a Ui,
563 ui: &'a Ui,
568 subcommand_args: &'a ArgMatches,
564 subcommand_args: &'a ArgMatches,
569 config: &'a Config,
565 config: &'a Config,
570 /// References inside `Result` is a bit peculiar but allow
566 /// References inside `Result` is a bit peculiar but allow
571 /// `invocation.repo?` to work out with `&CliInvocation` since this
567 /// `invocation.repo?` to work out with `&CliInvocation` since this
572 /// `Result` type is `Copy`.
568 /// `Result` type is `Copy`.
573 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
569 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
574 }
570 }
575
571
576 struct NoRepoInCwdError {
572 struct NoRepoInCwdError {
577 cwd: PathBuf,
573 cwd: PathBuf,
578 }
574 }
579
575
580 /// CLI arguments to be parsed "early" in order to be able to read
576 /// CLI arguments to be parsed "early" in order to be able to read
581 /// configuration before using Clap. Ideally we would also use Clap for this,
577 /// configuration before using Clap. Ideally we would also use Clap for this,
582 /// see <https://github.com/clap-rs/clap/discussions/2366>.
578 /// see <https://github.com/clap-rs/clap/discussions/2366>.
583 ///
579 ///
584 /// These arguments are still declared when we do use Clap later, so that Clap
580 /// These arguments are still declared when we do use Clap later, so that Clap
585 /// does not return an error for their presence.
581 /// does not return an error for their presence.
586 struct EarlyArgs {
582 struct EarlyArgs {
587 /// Values of all `--config` arguments. (Possibly none)
583 /// Values of all `--config` arguments. (Possibly none)
588 config: Vec<Vec<u8>>,
584 config: Vec<Vec<u8>>,
589 /// Value of all the `--color` argument, if any.
585 /// Value of all the `--color` argument, if any.
590 color: Option<Vec<u8>>,
586 color: Option<Vec<u8>>,
591 /// Value of the `-R` or `--repository` argument, if any.
587 /// Value of the `-R` or `--repository` argument, if any.
592 repo: Option<Vec<u8>>,
588 repo: Option<Vec<u8>>,
593 /// Value of the `--cwd` argument, if any.
589 /// Value of the `--cwd` argument, if any.
594 cwd: Option<Vec<u8>>,
590 cwd: Option<Vec<u8>>,
595 }
591 }
596
592
597 impl EarlyArgs {
593 impl EarlyArgs {
598 fn parse<'a>(args: impl IntoIterator<Item = &'a OsString>) -> Self {
594 fn parse<'a>(args: impl IntoIterator<Item = &'a OsString>) -> Self {
599 let mut args = args.into_iter().map(get_bytes_from_os_str);
595 let mut args = args.into_iter().map(get_bytes_from_os_str);
600 let mut config = Vec::new();
596 let mut config = Vec::new();
601 let mut color = None;
597 let mut color = None;
602 let mut repo = None;
598 let mut repo = None;
603 let mut cwd = None;
599 let mut cwd = None;
604 // Use `while let` instead of `for` so that we can also call
600 // Use `while let` instead of `for` so that we can also call
605 // `args.next()` inside the loop.
601 // `args.next()` inside the loop.
606 while let Some(arg) = args.next() {
602 while let Some(arg) = args.next() {
607 if arg == b"--config" {
603 if arg == b"--config" {
608 if let Some(value) = args.next() {
604 if let Some(value) = args.next() {
609 config.push(value)
605 config.push(value)
610 }
606 }
611 } else if let Some(value) = arg.drop_prefix(b"--config=") {
607 } else if let Some(value) = arg.drop_prefix(b"--config=") {
612 config.push(value.to_owned())
608 config.push(value.to_owned())
613 }
609 }
614
610
615 if arg == b"--color" {
611 if arg == b"--color" {
616 if let Some(value) = args.next() {
612 if let Some(value) = args.next() {
617 color = Some(value)
613 color = Some(value)
618 }
614 }
619 } else if let Some(value) = arg.drop_prefix(b"--color=") {
615 } else if let Some(value) = arg.drop_prefix(b"--color=") {
620 color = Some(value.to_owned())
616 color = Some(value.to_owned())
621 }
617 }
622
618
623 if arg == b"--cwd" {
619 if arg == b"--cwd" {
624 if let Some(value) = args.next() {
620 if let Some(value) = args.next() {
625 cwd = Some(value)
621 cwd = Some(value)
626 }
622 }
627 } else if let Some(value) = arg.drop_prefix(b"--cwd=") {
623 } else if let Some(value) = arg.drop_prefix(b"--cwd=") {
628 cwd = Some(value.to_owned())
624 cwd = Some(value.to_owned())
629 }
625 }
630
626
631 if arg == b"--repository" || arg == b"-R" {
627 if arg == b"--repository" || arg == b"-R" {
632 if let Some(value) = args.next() {
628 if let Some(value) = args.next() {
633 repo = Some(value)
629 repo = Some(value)
634 }
630 }
635 } else if let Some(value) = arg.drop_prefix(b"--repository=") {
631 } else if let Some(value) = arg.drop_prefix(b"--repository=") {
636 repo = Some(value.to_owned())
632 repo = Some(value.to_owned())
637 } else if let Some(value) = arg.drop_prefix(b"-R") {
633 } else if let Some(value) = arg.drop_prefix(b"-R") {
638 repo = Some(value.to_owned())
634 repo = Some(value.to_owned())
639 }
635 }
640 }
636 }
641 Self {
637 Self {
642 config,
638 config,
643 color,
639 color,
644 repo,
640 repo,
645 cwd,
641 cwd,
646 }
642 }
647 }
643 }
648 }
644 }
649
645
650 /// What to do when encountering some unsupported feature.
646 /// What to do when encountering some unsupported feature.
651 ///
647 ///
652 /// See `HgError::UnsupportedFeature` and `CommandError::UnsupportedFeature`.
648 /// See `HgError::UnsupportedFeature` and `CommandError::UnsupportedFeature`.
653 enum OnUnsupported {
649 enum OnUnsupported {
654 /// Print an error message describing what feature is not supported,
650 /// Print an error message describing what feature is not supported,
655 /// and exit with code 252.
651 /// and exit with code 252.
656 Abort,
652 Abort,
657 /// Silently exit with code 252.
653 /// Silently exit with code 252.
658 AbortSilent,
654 AbortSilent,
659 /// Try running a Python implementation
655 /// Try running a Python implementation
660 Fallback { executable: Option<Vec<u8>> },
656 Fallback { executable: Option<Vec<u8>> },
661 }
657 }
662
658
663 impl OnUnsupported {
659 impl OnUnsupported {
664 const DEFAULT: Self = OnUnsupported::Abort;
660 const DEFAULT: Self = OnUnsupported::Abort;
665
661
662 fn fallback_executable(config: &Config) -> Option<Vec<u8>> {
663 config
664 .get(b"rhg", b"fallback-executable")
665 .map(|x| x.to_owned())
666 }
667
668 fn fallback(config: &Config) -> Self {
669 OnUnsupported::Fallback {
670 executable: Self::fallback_executable(config),
671 }
672 }
673
666 fn from_config(config: &Config) -> Self {
674 fn from_config(config: &Config) -> Self {
667 match config
675 match config
668 .get(b"rhg", b"on-unsupported")
676 .get(b"rhg", b"on-unsupported")
669 .map(|value| value.to_ascii_lowercase())
677 .map(|value| value.to_ascii_lowercase())
670 .as_deref()
678 .as_deref()
671 {
679 {
672 Some(b"abort") => OnUnsupported::Abort,
680 Some(b"abort") => OnUnsupported::Abort,
673 Some(b"abort-silent") => OnUnsupported::AbortSilent,
681 Some(b"abort-silent") => OnUnsupported::AbortSilent,
674 Some(b"fallback") => OnUnsupported::Fallback {
682 Some(b"fallback") => Self::fallback(config),
675 executable: config
676 .get(b"rhg", b"fallback-executable")
677 .map(|x| x.to_owned()),
678 },
679 None => Self::DEFAULT,
683 None => Self::DEFAULT,
680 Some(_) => {
684 Some(_) => {
681 // TODO: warn about unknown config value
685 // TODO: warn about unknown config value
682 Self::DEFAULT
686 Self::DEFAULT
683 }
687 }
684 }
688 }
685 }
689 }
686 }
690 }
687
691
688 /// The `*` extension is an edge-case for config sub-options that apply to all
692 /// The `*` extension is an edge-case for config sub-options that apply to all
689 /// extensions. For now, only `:required` exists, but that may change in the
693 /// extensions. For now, only `:required` exists, but that may change in the
690 /// future.
694 /// future.
691 const SUPPORTED_EXTENSIONS: &[&[u8]] = &[
695 const SUPPORTED_EXTENSIONS: &[&[u8]] = &[
692 b"blackbox",
696 b"blackbox",
693 b"share",
697 b"share",
694 b"sparse",
698 b"sparse",
695 b"narrow",
699 b"narrow",
696 b"*",
700 b"*",
697 b"strip",
701 b"strip",
698 b"rebase",
702 b"rebase",
699 ];
703 ];
700
704
701 fn check_extensions(config: &Config) -> Result<(), CommandError> {
705 fn check_extensions(config: &Config) -> Result<(), CommandError> {
702 if let Some(b"*") = config.get(b"rhg", b"ignored-extensions") {
706 if let Some(b"*") = config.get(b"rhg", b"ignored-extensions") {
703 // All extensions are to be ignored, nothing to do here
707 // All extensions are to be ignored, nothing to do here
704 return Ok(());
708 return Ok(());
705 }
709 }
706
710
707 let enabled: HashSet<&[u8]> = config
711 let enabled: HashSet<&[u8]> = config
708 .iter_section(b"extensions")
712 .iter_section(b"extensions")
709 .filter_map(|(extension, value)| {
713 .filter_map(|(extension, value)| {
710 if value == b"!" {
714 if value == b"!" {
711 // Filter out disabled extensions
715 // Filter out disabled extensions
712 return None;
716 return None;
713 }
717 }
714 // Ignore extension suboptions. Only `required` exists for now.
718 // Ignore extension suboptions. Only `required` exists for now.
715 // `rhg` either supports an extension or doesn't, so it doesn't
719 // `rhg` either supports an extension or doesn't, so it doesn't
716 // make sense to consider the loading of an extension.
720 // make sense to consider the loading of an extension.
717 let actual_extension =
721 let actual_extension =
718 extension.split_2(b':').unwrap_or((extension, b"")).0;
722 extension.split_2(b':').unwrap_or((extension, b"")).0;
719 Some(actual_extension)
723 Some(actual_extension)
720 })
724 })
721 .collect();
725 .collect();
722
726
723 let mut unsupported = enabled;
727 let mut unsupported = enabled;
724 for supported in SUPPORTED_EXTENSIONS {
728 for supported in SUPPORTED_EXTENSIONS {
725 unsupported.remove(supported);
729 unsupported.remove(supported);
726 }
730 }
727
731
728 if let Some(ignored_list) = config.get_list(b"rhg", b"ignored-extensions")
732 if let Some(ignored_list) = config.get_list(b"rhg", b"ignored-extensions")
729 {
733 {
730 for ignored in ignored_list {
734 for ignored in ignored_list {
731 unsupported.remove(ignored.as_slice());
735 unsupported.remove(ignored.as_slice());
732 }
736 }
733 }
737 }
734
738
735 if unsupported.is_empty() {
739 if unsupported.is_empty() {
736 Ok(())
740 Ok(())
737 } else {
741 } else {
738 let mut unsupported: Vec<_> = unsupported.into_iter().collect();
742 let mut unsupported: Vec<_> = unsupported.into_iter().collect();
739 // Sort the extensions to get a stable output
743 // Sort the extensions to get a stable output
740 unsupported.sort();
744 unsupported.sort();
741 Err(CommandError::UnsupportedFeature {
745 Err(CommandError::UnsupportedFeature {
742 message: format_bytes!(
746 message: format_bytes!(
743 b"extensions: {} (consider adding them to 'rhg.ignored-extensions' config)",
747 b"extensions: {} (consider adding them to 'rhg.ignored-extensions' config)",
744 join(unsupported, b", ")
748 join(unsupported, b", ")
745 ),
749 ),
746 })
750 })
747 }
751 }
748 }
752 }
749
753
750 /// Array of tuples of (auto upgrade conf, feature conf, local requirement)
754 /// Array of tuples of (auto upgrade conf, feature conf, local requirement)
751 #[allow(clippy::type_complexity)]
755 #[allow(clippy::type_complexity)]
752 const AUTO_UPGRADES: &[((&str, &str), (&str, &str), &str)] = &[
756 const AUTO_UPGRADES: &[((&str, &str), (&str, &str), &str)] = &[
753 (
757 (
754 ("format", "use-share-safe.automatic-upgrade-of-mismatching-repositories"),
758 ("format", "use-share-safe.automatic-upgrade-of-mismatching-repositories"),
755 ("format", "use-share-safe"),
759 ("format", "use-share-safe"),
756 requirements::SHARESAFE_REQUIREMENT,
760 requirements::SHARESAFE_REQUIREMENT,
757 ),
761 ),
758 (
762 (
759 ("format", "use-dirstate-tracked-hint.automatic-upgrade-of-mismatching-repositories"),
763 ("format", "use-dirstate-tracked-hint.automatic-upgrade-of-mismatching-repositories"),
760 ("format", "use-dirstate-tracked-hint"),
764 ("format", "use-dirstate-tracked-hint"),
761 requirements::DIRSTATE_TRACKED_HINT_V1,
765 requirements::DIRSTATE_TRACKED_HINT_V1,
762 ),
766 ),
763 (
767 (
764 ("format", "use-dirstate-v2.automatic-upgrade-of-mismatching-repositories"),
768 ("format", "use-dirstate-v2.automatic-upgrade-of-mismatching-repositories"),
765 ("format", "use-dirstate-v2"),
769 ("format", "use-dirstate-v2"),
766 requirements::DIRSTATE_V2_REQUIREMENT,
770 requirements::DIRSTATE_V2_REQUIREMENT,
767 ),
771 ),
768 ];
772 ];
769
773
770 /// Mercurial allows users to automatically upgrade their repository.
774 /// Mercurial allows users to automatically upgrade their repository.
771 /// `rhg` does not have the ability to upgrade yet, so fallback if an upgrade
775 /// `rhg` does not have the ability to upgrade yet, so fallback if an upgrade
772 /// is needed.
776 /// is needed.
773 fn check_auto_upgrade(
777 fn check_auto_upgrade(
774 config: &Config,
778 config: &Config,
775 reqs: &HashSet<String>,
779 reqs: &HashSet<String>,
776 ) -> Result<(), CommandError> {
780 ) -> Result<(), CommandError> {
777 for (upgrade_conf, feature_conf, local_req) in AUTO_UPGRADES.iter() {
781 for (upgrade_conf, feature_conf, local_req) in AUTO_UPGRADES.iter() {
778 let auto_upgrade = config
782 let auto_upgrade = config
779 .get_bool(upgrade_conf.0.as_bytes(), upgrade_conf.1.as_bytes())?;
783 .get_bool(upgrade_conf.0.as_bytes(), upgrade_conf.1.as_bytes())?;
780
784
781 if auto_upgrade {
785 if auto_upgrade {
782 let want_it = config.get_bool(
786 let want_it = config.get_bool(
783 feature_conf.0.as_bytes(),
787 feature_conf.0.as_bytes(),
784 feature_conf.1.as_bytes(),
788 feature_conf.1.as_bytes(),
785 )?;
789 )?;
786 let have_it = reqs.contains(*local_req);
790 let have_it = reqs.contains(*local_req);
787
791
788 let action = match (want_it, have_it) {
792 let action = match (want_it, have_it) {
789 (true, false) => Some("upgrade"),
793 (true, false) => Some("upgrade"),
790 (false, true) => Some("downgrade"),
794 (false, true) => Some("downgrade"),
791 _ => None,
795 _ => None,
792 };
796 };
793 if let Some(action) = action {
797 if let Some(action) = action {
794 let message = format!(
798 let message = format!(
795 "automatic {} {}.{}",
799 "automatic {} {}.{}",
796 action, upgrade_conf.0, upgrade_conf.1
800 action, upgrade_conf.0, upgrade_conf.1
797 );
801 );
798 return Err(CommandError::unsupported(message));
802 return Err(CommandError::unsupported(message));
799 }
803 }
800 }
804 }
801 }
805 }
802 Ok(())
806 Ok(())
803 }
807 }
804
808
805 fn check_unsupported(
809 fn check_unsupported(
806 config: &Config,
810 config: &Config,
807 repo: Result<&Repo, &NoRepoInCwdError>,
811 repo: Result<&Repo, &NoRepoInCwdError>,
808 ) -> Result<(), CommandError> {
812 ) -> Result<(), CommandError> {
809 check_extensions(config)?;
813 check_extensions(config)?;
810
814
811 if std::env::var_os("HG_PENDING").is_some() {
815 if std::env::var_os("HG_PENDING").is_some() {
812 // TODO: only if the value is `== repo.working_directory`?
816 // TODO: only if the value is `== repo.working_directory`?
813 // What about relative v.s. absolute paths?
817 // What about relative v.s. absolute paths?
814 Err(CommandError::unsupported("$HG_PENDING"))?
818 Err(CommandError::unsupported("$HG_PENDING"))?
815 }
819 }
816
820
817 if let Ok(repo) = repo {
821 if let Ok(repo) = repo {
818 if repo.has_subrepos()? {
822 if repo.has_subrepos()? {
819 Err(CommandError::unsupported("sub-repositories"))?
823 Err(CommandError::unsupported("sub-repositories"))?
820 }
824 }
821 check_auto_upgrade(config, repo.requirements())?;
825 check_auto_upgrade(config, repo.requirements())?;
822 }
826 }
823
827
824 if config.has_non_empty_section(b"encode") {
828 if config.has_non_empty_section(b"encode") {
825 Err(CommandError::unsupported("[encode] config"))?
829 Err(CommandError::unsupported("[encode] config"))?
826 }
830 }
827
831
828 if config.has_non_empty_section(b"decode") {
832 if config.has_non_empty_section(b"decode") {
829 Err(CommandError::unsupported("[decode] config"))?
833 Err(CommandError::unsupported("[decode] config"))?
830 }
834 }
831
835
832 Ok(())
836 Ok(())
833 }
837 }
General Comments 0
You need to be logged in to leave comments. Login now