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