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