##// END OF EJS Templates
rhg: add support for ignoring all extensions...
Raphaël Gomès -
r49829:6b31c067 default
parent child Browse files
Show More
@@ -1,708 +1,713 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::exit_codes;
10 use hg::exit_codes;
11 use hg::repo::{Repo, RepoError};
11 use hg::repo::{Repo, RepoError};
12 use hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes};
12 use hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes};
13 use hg::utils::SliceExt;
13 use hg::utils::SliceExt;
14 use std::collections::HashSet;
14 use std::collections::HashSet;
15 use std::ffi::OsString;
15 use std::ffi::OsString;
16 use std::path::PathBuf;
16 use std::path::PathBuf;
17 use std::process::Command;
17 use std::process::Command;
18
18
19 mod blackbox;
19 mod blackbox;
20 mod color;
20 mod color;
21 mod error;
21 mod error;
22 mod ui;
22 mod ui;
23 pub mod utils {
23 pub mod utils {
24 pub mod path_utils;
24 pub mod path_utils;
25 }
25 }
26
26
27 fn main_with_result(
27 fn main_with_result(
28 process_start_time: &blackbox::ProcessStartTime,
28 process_start_time: &blackbox::ProcessStartTime,
29 ui: &ui::Ui,
29 ui: &ui::Ui,
30 repo: Result<&Repo, &NoRepoInCwdError>,
30 repo: Result<&Repo, &NoRepoInCwdError>,
31 config: &Config,
31 config: &Config,
32 ) -> Result<(), CommandError> {
32 ) -> Result<(), CommandError> {
33 check_unsupported(config, repo)?;
33 check_unsupported(config, repo)?;
34
34
35 let app = App::new("rhg")
35 let app = App::new("rhg")
36 .global_setting(AppSettings::AllowInvalidUtf8)
36 .global_setting(AppSettings::AllowInvalidUtf8)
37 .global_setting(AppSettings::DisableVersion)
37 .global_setting(AppSettings::DisableVersion)
38 .setting(AppSettings::SubcommandRequired)
38 .setting(AppSettings::SubcommandRequired)
39 .setting(AppSettings::VersionlessSubcommands)
39 .setting(AppSettings::VersionlessSubcommands)
40 .arg(
40 .arg(
41 Arg::with_name("repository")
41 Arg::with_name("repository")
42 .help("repository root directory")
42 .help("repository root directory")
43 .short("-R")
43 .short("-R")
44 .long("--repository")
44 .long("--repository")
45 .value_name("REPO")
45 .value_name("REPO")
46 .takes_value(true)
46 .takes_value(true)
47 // Both ok: `hg -R ./foo log` or `hg log -R ./foo`
47 // Both ok: `hg -R ./foo log` or `hg log -R ./foo`
48 .global(true),
48 .global(true),
49 )
49 )
50 .arg(
50 .arg(
51 Arg::with_name("config")
51 Arg::with_name("config")
52 .help("set/override config option (use 'section.name=value')")
52 .help("set/override config option (use 'section.name=value')")
53 .long("--config")
53 .long("--config")
54 .value_name("CONFIG")
54 .value_name("CONFIG")
55 .takes_value(true)
55 .takes_value(true)
56 .global(true)
56 .global(true)
57 // Ok: `--config section.key1=val --config section.key2=val2`
57 // Ok: `--config section.key1=val --config section.key2=val2`
58 .multiple(true)
58 .multiple(true)
59 // Not ok: `--config section.key1=val section.key2=val2`
59 // Not ok: `--config section.key1=val section.key2=val2`
60 .number_of_values(1),
60 .number_of_values(1),
61 )
61 )
62 .arg(
62 .arg(
63 Arg::with_name("cwd")
63 Arg::with_name("cwd")
64 .help("change working directory")
64 .help("change working directory")
65 .long("--cwd")
65 .long("--cwd")
66 .value_name("DIR")
66 .value_name("DIR")
67 .takes_value(true)
67 .takes_value(true)
68 .global(true),
68 .global(true),
69 )
69 )
70 .arg(
70 .arg(
71 Arg::with_name("color")
71 Arg::with_name("color")
72 .help("when to colorize (boolean, always, auto, never, or debug)")
72 .help("when to colorize (boolean, always, auto, never, or debug)")
73 .long("--color")
73 .long("--color")
74 .value_name("TYPE")
74 .value_name("TYPE")
75 .takes_value(true)
75 .takes_value(true)
76 .global(true),
76 .global(true),
77 )
77 )
78 .version("0.0.1");
78 .version("0.0.1");
79 let app = add_subcommand_args(app);
79 let app = add_subcommand_args(app);
80
80
81 let matches = app.clone().get_matches_safe()?;
81 let matches = app.clone().get_matches_safe()?;
82
82
83 let (subcommand_name, subcommand_matches) = matches.subcommand();
83 let (subcommand_name, subcommand_matches) = matches.subcommand();
84
84
85 // Mercurial allows users to define "defaults" for commands, fallback
85 // Mercurial allows users to define "defaults" for commands, fallback
86 // if a default is detected for the current command
86 // if a default is detected for the current command
87 let defaults = config.get_str(b"defaults", subcommand_name.as_bytes());
87 let defaults = config.get_str(b"defaults", subcommand_name.as_bytes());
88 if defaults?.is_some() {
88 if defaults?.is_some() {
89 let msg = "`defaults` config set";
89 let msg = "`defaults` config set";
90 return Err(CommandError::unsupported(msg));
90 return Err(CommandError::unsupported(msg));
91 }
91 }
92
92
93 for prefix in ["pre", "post", "fail"].iter() {
93 for prefix in ["pre", "post", "fail"].iter() {
94 // Mercurial allows users to define generic hooks for commands,
94 // Mercurial allows users to define generic hooks for commands,
95 // fallback if any are detected
95 // fallback if any are detected
96 let item = format!("{}-{}", prefix, subcommand_name);
96 let item = format!("{}-{}", prefix, subcommand_name);
97 let hook_for_command = config.get_str(b"hooks", item.as_bytes())?;
97 let hook_for_command = config.get_str(b"hooks", item.as_bytes())?;
98 if hook_for_command.is_some() {
98 if hook_for_command.is_some() {
99 let msg = format!("{}-{} hook defined", prefix, subcommand_name);
99 let msg = format!("{}-{} hook defined", prefix, subcommand_name);
100 return Err(CommandError::unsupported(msg));
100 return Err(CommandError::unsupported(msg));
101 }
101 }
102 }
102 }
103 let run = subcommand_run_fn(subcommand_name)
103 let run = subcommand_run_fn(subcommand_name)
104 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
104 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
105 let subcommand_args = subcommand_matches
105 let subcommand_args = subcommand_matches
106 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
106 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
107
107
108 let invocation = CliInvocation {
108 let invocation = CliInvocation {
109 ui,
109 ui,
110 subcommand_args,
110 subcommand_args,
111 config,
111 config,
112 repo,
112 repo,
113 };
113 };
114
114
115 if let Ok(repo) = repo {
115 if let Ok(repo) = repo {
116 // We don't support subrepos, fallback if the subrepos file is present
116 // We don't support subrepos, fallback if the subrepos file is present
117 if repo.working_directory_vfs().join(".hgsub").exists() {
117 if repo.working_directory_vfs().join(".hgsub").exists() {
118 let msg = "subrepos (.hgsub is present)";
118 let msg = "subrepos (.hgsub is present)";
119 return Err(CommandError::unsupported(msg));
119 return Err(CommandError::unsupported(msg));
120 }
120 }
121 }
121 }
122
122
123 if config.is_extension_enabled(b"blackbox") {
123 if config.is_extension_enabled(b"blackbox") {
124 let blackbox =
124 let blackbox =
125 blackbox::Blackbox::new(&invocation, process_start_time)?;
125 blackbox::Blackbox::new(&invocation, process_start_time)?;
126 blackbox.log_command_start();
126 blackbox.log_command_start();
127 let result = run(&invocation);
127 let result = run(&invocation);
128 blackbox.log_command_end(exit_code(
128 blackbox.log_command_end(exit_code(
129 &result,
129 &result,
130 // TODO: show a warning or combine with original error if
130 // TODO: show a warning or combine with original error if
131 // `get_bool` returns an error
131 // `get_bool` returns an error
132 config
132 config
133 .get_bool(b"ui", b"detailed-exit-code")
133 .get_bool(b"ui", b"detailed-exit-code")
134 .unwrap_or(false),
134 .unwrap_or(false),
135 ));
135 ));
136 result
136 result
137 } else {
137 } else {
138 run(&invocation)
138 run(&invocation)
139 }
139 }
140 }
140 }
141
141
142 fn main() {
142 fn main() {
143 // Run this first, before we find out if the blackbox extension is even
143 // Run this first, before we find out if the blackbox extension is even
144 // enabled, in order to include everything in-between in the duration
144 // enabled, in order to include everything in-between in the duration
145 // measurements. Reading config files can be slow if they’re on NFS.
145 // measurements. Reading config files can be slow if they’re on NFS.
146 let process_start_time = blackbox::ProcessStartTime::now();
146 let process_start_time = blackbox::ProcessStartTime::now();
147
147
148 env_logger::init();
148 env_logger::init();
149
149
150 let early_args = EarlyArgs::parse(std::env::args_os());
150 let early_args = EarlyArgs::parse(std::env::args_os());
151
151
152 let initial_current_dir = early_args.cwd.map(|cwd| {
152 let initial_current_dir = early_args.cwd.map(|cwd| {
153 let cwd = get_path_from_bytes(&cwd);
153 let cwd = get_path_from_bytes(&cwd);
154 std::env::current_dir()
154 std::env::current_dir()
155 .and_then(|initial| {
155 .and_then(|initial| {
156 std::env::set_current_dir(cwd)?;
156 std::env::set_current_dir(cwd)?;
157 Ok(initial)
157 Ok(initial)
158 })
158 })
159 .unwrap_or_else(|error| {
159 .unwrap_or_else(|error| {
160 exit(
160 exit(
161 &None,
161 &None,
162 &Ui::new_infallible(&Config::empty()),
162 &Ui::new_infallible(&Config::empty()),
163 OnUnsupported::Abort,
163 OnUnsupported::Abort,
164 Err(CommandError::abort(format!(
164 Err(CommandError::abort(format!(
165 "abort: {}: '{}'",
165 "abort: {}: '{}'",
166 error,
166 error,
167 cwd.display()
167 cwd.display()
168 ))),
168 ))),
169 false,
169 false,
170 )
170 )
171 })
171 })
172 });
172 });
173
173
174 let mut non_repo_config =
174 let mut non_repo_config =
175 Config::load_non_repo().unwrap_or_else(|error| {
175 Config::load_non_repo().unwrap_or_else(|error| {
176 // Normally this is decided based on config, but we don’t have that
176 // Normally this is decided based on config, but we don’t have that
177 // available. As of this writing config loading never returns an
177 // available. As of this writing config loading never returns an
178 // "unsupported" error but that is not enforced by the type system.
178 // "unsupported" error but that is not enforced by the type system.
179 let on_unsupported = OnUnsupported::Abort;
179 let on_unsupported = OnUnsupported::Abort;
180
180
181 exit(
181 exit(
182 &initial_current_dir,
182 &initial_current_dir,
183 &Ui::new_infallible(&Config::empty()),
183 &Ui::new_infallible(&Config::empty()),
184 on_unsupported,
184 on_unsupported,
185 Err(error.into()),
185 Err(error.into()),
186 false,
186 false,
187 )
187 )
188 });
188 });
189
189
190 non_repo_config
190 non_repo_config
191 .load_cli_args(early_args.config, early_args.color)
191 .load_cli_args(early_args.config, early_args.color)
192 .unwrap_or_else(|error| {
192 .unwrap_or_else(|error| {
193 exit(
193 exit(
194 &initial_current_dir,
194 &initial_current_dir,
195 &Ui::new_infallible(&non_repo_config),
195 &Ui::new_infallible(&non_repo_config),
196 OnUnsupported::from_config(&non_repo_config),
196 OnUnsupported::from_config(&non_repo_config),
197 Err(error.into()),
197 Err(error.into()),
198 non_repo_config
198 non_repo_config
199 .get_bool(b"ui", b"detailed-exit-code")
199 .get_bool(b"ui", b"detailed-exit-code")
200 .unwrap_or(false),
200 .unwrap_or(false),
201 )
201 )
202 });
202 });
203
203
204 if let Some(repo_path_bytes) = &early_args.repo {
204 if let Some(repo_path_bytes) = &early_args.repo {
205 lazy_static::lazy_static! {
205 lazy_static::lazy_static! {
206 static ref SCHEME_RE: regex::bytes::Regex =
206 static ref SCHEME_RE: regex::bytes::Regex =
207 // Same as `_matchscheme` in `mercurial/util.py`
207 // Same as `_matchscheme` in `mercurial/util.py`
208 regex::bytes::Regex::new("^[a-zA-Z0-9+.\\-]+:").unwrap();
208 regex::bytes::Regex::new("^[a-zA-Z0-9+.\\-]+:").unwrap();
209 }
209 }
210 if SCHEME_RE.is_match(&repo_path_bytes) {
210 if SCHEME_RE.is_match(&repo_path_bytes) {
211 exit(
211 exit(
212 &initial_current_dir,
212 &initial_current_dir,
213 &Ui::new_infallible(&non_repo_config),
213 &Ui::new_infallible(&non_repo_config),
214 OnUnsupported::from_config(&non_repo_config),
214 OnUnsupported::from_config(&non_repo_config),
215 Err(CommandError::UnsupportedFeature {
215 Err(CommandError::UnsupportedFeature {
216 message: format_bytes!(
216 message: format_bytes!(
217 b"URL-like --repository {}",
217 b"URL-like --repository {}",
218 repo_path_bytes
218 repo_path_bytes
219 ),
219 ),
220 }),
220 }),
221 // TODO: show a warning or combine with original error if
221 // TODO: show a warning or combine with original error if
222 // `get_bool` returns an error
222 // `get_bool` returns an error
223 non_repo_config
223 non_repo_config
224 .get_bool(b"ui", b"detailed-exit-code")
224 .get_bool(b"ui", b"detailed-exit-code")
225 .unwrap_or(false),
225 .unwrap_or(false),
226 )
226 )
227 }
227 }
228 }
228 }
229 let repo_arg = early_args.repo.unwrap_or(Vec::new());
229 let repo_arg = early_args.repo.unwrap_or(Vec::new());
230 let repo_path: Option<PathBuf> = {
230 let repo_path: Option<PathBuf> = {
231 if repo_arg.is_empty() {
231 if repo_arg.is_empty() {
232 None
232 None
233 } else {
233 } else {
234 let local_config = {
234 let local_config = {
235 if std::env::var_os("HGRCSKIPREPO").is_none() {
235 if std::env::var_os("HGRCSKIPREPO").is_none() {
236 // TODO: handle errors from find_repo_root
236 // TODO: handle errors from find_repo_root
237 if let Ok(current_dir_path) = Repo::find_repo_root() {
237 if let Ok(current_dir_path) = Repo::find_repo_root() {
238 let config_files = vec![
238 let config_files = vec![
239 ConfigSource::AbsPath(
239 ConfigSource::AbsPath(
240 current_dir_path.join(".hg/hgrc"),
240 current_dir_path.join(".hg/hgrc"),
241 ),
241 ),
242 ConfigSource::AbsPath(
242 ConfigSource::AbsPath(
243 current_dir_path.join(".hg/hgrc-not-shared"),
243 current_dir_path.join(".hg/hgrc-not-shared"),
244 ),
244 ),
245 ];
245 ];
246 // TODO: handle errors from
246 // TODO: handle errors from
247 // `load_from_explicit_sources`
247 // `load_from_explicit_sources`
248 Config::load_from_explicit_sources(config_files).ok()
248 Config::load_from_explicit_sources(config_files).ok()
249 } else {
249 } else {
250 None
250 None
251 }
251 }
252 } else {
252 } else {
253 None
253 None
254 }
254 }
255 };
255 };
256
256
257 let non_repo_config_val = {
257 let non_repo_config_val = {
258 let non_repo_val = non_repo_config.get(b"paths", &repo_arg);
258 let non_repo_val = non_repo_config.get(b"paths", &repo_arg);
259 match &non_repo_val {
259 match &non_repo_val {
260 Some(val) if val.len() > 0 => home::home_dir()
260 Some(val) if val.len() > 0 => home::home_dir()
261 .unwrap_or_else(|| PathBuf::from("~"))
261 .unwrap_or_else(|| PathBuf::from("~"))
262 .join(get_path_from_bytes(val))
262 .join(get_path_from_bytes(val))
263 .canonicalize()
263 .canonicalize()
264 // TODO: handle error and make it similar to python
264 // TODO: handle error and make it similar to python
265 // implementation maybe?
265 // implementation maybe?
266 .ok(),
266 .ok(),
267 _ => None,
267 _ => None,
268 }
268 }
269 };
269 };
270
270
271 let config_val = match &local_config {
271 let config_val = match &local_config {
272 None => non_repo_config_val,
272 None => non_repo_config_val,
273 Some(val) => {
273 Some(val) => {
274 let local_config_val = val.get(b"paths", &repo_arg);
274 let local_config_val = val.get(b"paths", &repo_arg);
275 match &local_config_val {
275 match &local_config_val {
276 Some(val) if val.len() > 0 => {
276 Some(val) if val.len() > 0 => {
277 // presence of a local_config assures that
277 // presence of a local_config assures that
278 // current_dir
278 // current_dir
279 // wont result in an Error
279 // wont result in an Error
280 let canpath = hg::utils::current_dir()
280 let canpath = hg::utils::current_dir()
281 .unwrap()
281 .unwrap()
282 .join(get_path_from_bytes(val))
282 .join(get_path_from_bytes(val))
283 .canonicalize();
283 .canonicalize();
284 canpath.ok().or(non_repo_config_val)
284 canpath.ok().or(non_repo_config_val)
285 }
285 }
286 _ => non_repo_config_val,
286 _ => non_repo_config_val,
287 }
287 }
288 }
288 }
289 };
289 };
290 config_val.or(Some(get_path_from_bytes(&repo_arg).to_path_buf()))
290 config_val.or(Some(get_path_from_bytes(&repo_arg).to_path_buf()))
291 }
291 }
292 };
292 };
293
293
294 let repo_result = match Repo::find(&non_repo_config, repo_path.to_owned())
294 let repo_result = match Repo::find(&non_repo_config, repo_path.to_owned())
295 {
295 {
296 Ok(repo) => Ok(repo),
296 Ok(repo) => Ok(repo),
297 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
297 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
298 // Not finding a repo is not fatal yet, if `-R` was not given
298 // Not finding a repo is not fatal yet, if `-R` was not given
299 Err(NoRepoInCwdError { cwd: at })
299 Err(NoRepoInCwdError { cwd: at })
300 }
300 }
301 Err(error) => exit(
301 Err(error) => exit(
302 &initial_current_dir,
302 &initial_current_dir,
303 &Ui::new_infallible(&non_repo_config),
303 &Ui::new_infallible(&non_repo_config),
304 OnUnsupported::from_config(&non_repo_config),
304 OnUnsupported::from_config(&non_repo_config),
305 Err(error.into()),
305 Err(error.into()),
306 // TODO: show a warning or combine with original error if
306 // TODO: show a warning or combine with original error if
307 // `get_bool` returns an error
307 // `get_bool` returns an error
308 non_repo_config
308 non_repo_config
309 .get_bool(b"ui", b"detailed-exit-code")
309 .get_bool(b"ui", b"detailed-exit-code")
310 .unwrap_or(false),
310 .unwrap_or(false),
311 ),
311 ),
312 };
312 };
313
313
314 let config = if let Ok(repo) = &repo_result {
314 let config = if let Ok(repo) = &repo_result {
315 repo.config()
315 repo.config()
316 } else {
316 } else {
317 &non_repo_config
317 &non_repo_config
318 };
318 };
319 let ui = Ui::new(&config).unwrap_or_else(|error| {
319 let ui = Ui::new(&config).unwrap_or_else(|error| {
320 exit(
320 exit(
321 &initial_current_dir,
321 &initial_current_dir,
322 &Ui::new_infallible(&config),
322 &Ui::new_infallible(&config),
323 OnUnsupported::from_config(&config),
323 OnUnsupported::from_config(&config),
324 Err(error.into()),
324 Err(error.into()),
325 config
325 config
326 .get_bool(b"ui", b"detailed-exit-code")
326 .get_bool(b"ui", b"detailed-exit-code")
327 .unwrap_or(false),
327 .unwrap_or(false),
328 )
328 )
329 });
329 });
330 let on_unsupported = OnUnsupported::from_config(config);
330 let on_unsupported = OnUnsupported::from_config(config);
331
331
332 let result = main_with_result(
332 let result = main_with_result(
333 &process_start_time,
333 &process_start_time,
334 &ui,
334 &ui,
335 repo_result.as_ref(),
335 repo_result.as_ref(),
336 config,
336 config,
337 );
337 );
338 exit(
338 exit(
339 &initial_current_dir,
339 &initial_current_dir,
340 &ui,
340 &ui,
341 on_unsupported,
341 on_unsupported,
342 result,
342 result,
343 // TODO: show a warning or combine with original error if `get_bool`
343 // TODO: show a warning or combine with original error if `get_bool`
344 // returns an error
344 // returns an error
345 config
345 config
346 .get_bool(b"ui", b"detailed-exit-code")
346 .get_bool(b"ui", b"detailed-exit-code")
347 .unwrap_or(false),
347 .unwrap_or(false),
348 )
348 )
349 }
349 }
350
350
351 fn exit_code(
351 fn exit_code(
352 result: &Result<(), CommandError>,
352 result: &Result<(), CommandError>,
353 use_detailed_exit_code: bool,
353 use_detailed_exit_code: bool,
354 ) -> i32 {
354 ) -> i32 {
355 match result {
355 match result {
356 Ok(()) => exit_codes::OK,
356 Ok(()) => exit_codes::OK,
357 Err(CommandError::Abort {
357 Err(CommandError::Abort {
358 message: _,
358 message: _,
359 detailed_exit_code,
359 detailed_exit_code,
360 }) => {
360 }) => {
361 if use_detailed_exit_code {
361 if use_detailed_exit_code {
362 *detailed_exit_code
362 *detailed_exit_code
363 } else {
363 } else {
364 exit_codes::ABORT
364 exit_codes::ABORT
365 }
365 }
366 }
366 }
367 Err(CommandError::Unsuccessful) => exit_codes::UNSUCCESSFUL,
367 Err(CommandError::Unsuccessful) => exit_codes::UNSUCCESSFUL,
368
368
369 // Exit with a specific code and no error message to let a potential
369 // Exit with a specific code and no error message to let a potential
370 // wrapper script fallback to Python-based Mercurial.
370 // wrapper script fallback to Python-based Mercurial.
371 Err(CommandError::UnsupportedFeature { .. }) => {
371 Err(CommandError::UnsupportedFeature { .. }) => {
372 exit_codes::UNIMPLEMENTED
372 exit_codes::UNIMPLEMENTED
373 }
373 }
374 }
374 }
375 }
375 }
376
376
377 fn exit(
377 fn exit(
378 initial_current_dir: &Option<PathBuf>,
378 initial_current_dir: &Option<PathBuf>,
379 ui: &Ui,
379 ui: &Ui,
380 mut on_unsupported: OnUnsupported,
380 mut on_unsupported: OnUnsupported,
381 result: Result<(), CommandError>,
381 result: Result<(), CommandError>,
382 use_detailed_exit_code: bool,
382 use_detailed_exit_code: bool,
383 ) -> ! {
383 ) -> ! {
384 if let (
384 if let (
385 OnUnsupported::Fallback { executable },
385 OnUnsupported::Fallback { executable },
386 Err(CommandError::UnsupportedFeature { message }),
386 Err(CommandError::UnsupportedFeature { message }),
387 ) = (&on_unsupported, &result)
387 ) = (&on_unsupported, &result)
388 {
388 {
389 let mut args = std::env::args_os();
389 let mut args = std::env::args_os();
390 let executable = match executable {
390 let executable = match executable {
391 None => {
391 None => {
392 exit_no_fallback(
392 exit_no_fallback(
393 ui,
393 ui,
394 OnUnsupported::Abort,
394 OnUnsupported::Abort,
395 Err(CommandError::abort(
395 Err(CommandError::abort(
396 "abort: 'rhg.on-unsupported=fallback' without \
396 "abort: 'rhg.on-unsupported=fallback' without \
397 'rhg.fallback-executable' set.",
397 'rhg.fallback-executable' set.",
398 )),
398 )),
399 false,
399 false,
400 );
400 );
401 }
401 }
402 Some(executable) => executable,
402 Some(executable) => executable,
403 };
403 };
404 let executable_path = get_path_from_bytes(&executable);
404 let executable_path = get_path_from_bytes(&executable);
405 let this_executable = args.next().expect("exepcted argv[0] to exist");
405 let this_executable = args.next().expect("exepcted argv[0] to exist");
406 if executable_path == &PathBuf::from(this_executable) {
406 if executable_path == &PathBuf::from(this_executable) {
407 // Avoid spawning infinitely many processes until resource
407 // Avoid spawning infinitely many processes until resource
408 // exhaustion.
408 // exhaustion.
409 let _ = ui.write_stderr(&format_bytes!(
409 let _ = ui.write_stderr(&format_bytes!(
410 b"Blocking recursive fallback. The 'rhg.fallback-executable = {}' config \
410 b"Blocking recursive fallback. The 'rhg.fallback-executable = {}' config \
411 points to `rhg` itself.\n",
411 points to `rhg` itself.\n",
412 executable
412 executable
413 ));
413 ));
414 on_unsupported = OnUnsupported::Abort
414 on_unsupported = OnUnsupported::Abort
415 } else {
415 } else {
416 log::debug!("falling back (see trace-level log)");
416 log::debug!("falling back (see trace-level log)");
417 log::trace!("{}", local_to_utf8(message));
417 log::trace!("{}", local_to_utf8(message));
418 // `args` is now `argv[1..]` since we’ve already consumed
418 // `args` is now `argv[1..]` since we’ve already consumed
419 // `argv[0]`
419 // `argv[0]`
420 let mut command = Command::new(executable_path);
420 let mut command = Command::new(executable_path);
421 command.args(args);
421 command.args(args);
422 if let Some(initial) = initial_current_dir {
422 if let Some(initial) = initial_current_dir {
423 command.current_dir(initial);
423 command.current_dir(initial);
424 }
424 }
425 let result = command.status();
425 let result = command.status();
426 match result {
426 match result {
427 Ok(status) => std::process::exit(
427 Ok(status) => std::process::exit(
428 status.code().unwrap_or(exit_codes::ABORT),
428 status.code().unwrap_or(exit_codes::ABORT),
429 ),
429 ),
430 Err(error) => {
430 Err(error) => {
431 let _ = ui.write_stderr(&format_bytes!(
431 let _ = ui.write_stderr(&format_bytes!(
432 b"tried to fall back to a '{}' sub-process but got error {}\n",
432 b"tried to fall back to a '{}' sub-process but got error {}\n",
433 executable, format_bytes::Utf8(error)
433 executable, format_bytes::Utf8(error)
434 ));
434 ));
435 on_unsupported = OnUnsupported::Abort
435 on_unsupported = OnUnsupported::Abort
436 }
436 }
437 }
437 }
438 }
438 }
439 }
439 }
440 exit_no_fallback(ui, on_unsupported, result, use_detailed_exit_code)
440 exit_no_fallback(ui, on_unsupported, result, use_detailed_exit_code)
441 }
441 }
442
442
443 fn exit_no_fallback(
443 fn exit_no_fallback(
444 ui: &Ui,
444 ui: &Ui,
445 on_unsupported: OnUnsupported,
445 on_unsupported: OnUnsupported,
446 result: Result<(), CommandError>,
446 result: Result<(), CommandError>,
447 use_detailed_exit_code: bool,
447 use_detailed_exit_code: bool,
448 ) -> ! {
448 ) -> ! {
449 match &result {
449 match &result {
450 Ok(_) => {}
450 Ok(_) => {}
451 Err(CommandError::Unsuccessful) => {}
451 Err(CommandError::Unsuccessful) => {}
452 Err(CommandError::Abort {
452 Err(CommandError::Abort {
453 message,
453 message,
454 detailed_exit_code: _,
454 detailed_exit_code: _,
455 }) => {
455 }) => {
456 if !message.is_empty() {
456 if !message.is_empty() {
457 // Ignore errors when writing to stderr, we’re already exiting
457 // Ignore errors when writing to stderr, we’re already exiting
458 // with failure code so there’s not much more we can do.
458 // with failure code so there’s not much more we can do.
459 let _ = ui.write_stderr(&format_bytes!(b"{}\n", message));
459 let _ = ui.write_stderr(&format_bytes!(b"{}\n", message));
460 }
460 }
461 }
461 }
462 Err(CommandError::UnsupportedFeature { message }) => {
462 Err(CommandError::UnsupportedFeature { message }) => {
463 match on_unsupported {
463 match on_unsupported {
464 OnUnsupported::Abort => {
464 OnUnsupported::Abort => {
465 let _ = ui.write_stderr(&format_bytes!(
465 let _ = ui.write_stderr(&format_bytes!(
466 b"unsupported feature: {}\n",
466 b"unsupported feature: {}\n",
467 message
467 message
468 ));
468 ));
469 }
469 }
470 OnUnsupported::AbortSilent => {}
470 OnUnsupported::AbortSilent => {}
471 OnUnsupported::Fallback { .. } => unreachable!(),
471 OnUnsupported::Fallback { .. } => unreachable!(),
472 }
472 }
473 }
473 }
474 }
474 }
475 std::process::exit(exit_code(&result, use_detailed_exit_code))
475 std::process::exit(exit_code(&result, use_detailed_exit_code))
476 }
476 }
477
477
478 macro_rules! subcommands {
478 macro_rules! subcommands {
479 ($( $command: ident )+) => {
479 ($( $command: ident )+) => {
480 mod commands {
480 mod commands {
481 $(
481 $(
482 pub mod $command;
482 pub mod $command;
483 )+
483 )+
484 }
484 }
485
485
486 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
486 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
487 app
487 app
488 $(
488 $(
489 .subcommand(commands::$command::args())
489 .subcommand(commands::$command::args())
490 )+
490 )+
491 }
491 }
492
492
493 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
493 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
494
494
495 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
495 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
496 match name {
496 match name {
497 $(
497 $(
498 stringify!($command) => Some(commands::$command::run),
498 stringify!($command) => Some(commands::$command::run),
499 )+
499 )+
500 _ => None,
500 _ => None,
501 }
501 }
502 }
502 }
503 };
503 };
504 }
504 }
505
505
506 subcommands! {
506 subcommands! {
507 cat
507 cat
508 debugdata
508 debugdata
509 debugrequirements
509 debugrequirements
510 debugignorerhg
510 debugignorerhg
511 files
511 files
512 root
512 root
513 config
513 config
514 status
514 status
515 }
515 }
516
516
517 pub struct CliInvocation<'a> {
517 pub struct CliInvocation<'a> {
518 ui: &'a Ui,
518 ui: &'a Ui,
519 subcommand_args: &'a ArgMatches<'a>,
519 subcommand_args: &'a ArgMatches<'a>,
520 config: &'a Config,
520 config: &'a Config,
521 /// References inside `Result` is a bit peculiar but allow
521 /// References inside `Result` is a bit peculiar but allow
522 /// `invocation.repo?` to work out with `&CliInvocation` since this
522 /// `invocation.repo?` to work out with `&CliInvocation` since this
523 /// `Result` type is `Copy`.
523 /// `Result` type is `Copy`.
524 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
524 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
525 }
525 }
526
526
527 struct NoRepoInCwdError {
527 struct NoRepoInCwdError {
528 cwd: PathBuf,
528 cwd: PathBuf,
529 }
529 }
530
530
531 /// CLI arguments to be parsed "early" in order to be able to read
531 /// CLI arguments to be parsed "early" in order to be able to read
532 /// configuration before using Clap. Ideally we would also use Clap for this,
532 /// configuration before using Clap. Ideally we would also use Clap for this,
533 /// see <https://github.com/clap-rs/clap/discussions/2366>.
533 /// see <https://github.com/clap-rs/clap/discussions/2366>.
534 ///
534 ///
535 /// These arguments are still declared when we do use Clap later, so that Clap
535 /// These arguments are still declared when we do use Clap later, so that Clap
536 /// does not return an error for their presence.
536 /// does not return an error for their presence.
537 struct EarlyArgs {
537 struct EarlyArgs {
538 /// Values of all `--config` arguments. (Possibly none)
538 /// Values of all `--config` arguments. (Possibly none)
539 config: Vec<Vec<u8>>,
539 config: Vec<Vec<u8>>,
540 /// Value of all the `--color` argument, if any.
540 /// Value of all the `--color` argument, if any.
541 color: Option<Vec<u8>>,
541 color: Option<Vec<u8>>,
542 /// Value of the `-R` or `--repository` argument, if any.
542 /// Value of the `-R` or `--repository` argument, if any.
543 repo: Option<Vec<u8>>,
543 repo: Option<Vec<u8>>,
544 /// Value of the `--cwd` argument, if any.
544 /// Value of the `--cwd` argument, if any.
545 cwd: Option<Vec<u8>>,
545 cwd: Option<Vec<u8>>,
546 }
546 }
547
547
548 impl EarlyArgs {
548 impl EarlyArgs {
549 fn parse(args: impl IntoIterator<Item = OsString>) -> Self {
549 fn parse(args: impl IntoIterator<Item = OsString>) -> Self {
550 let mut args = args.into_iter().map(get_bytes_from_os_str);
550 let mut args = args.into_iter().map(get_bytes_from_os_str);
551 let mut config = Vec::new();
551 let mut config = Vec::new();
552 let mut color = None;
552 let mut color = None;
553 let mut repo = None;
553 let mut repo = None;
554 let mut cwd = None;
554 let mut cwd = None;
555 // Use `while let` instead of `for` so that we can also call
555 // Use `while let` instead of `for` so that we can also call
556 // `args.next()` inside the loop.
556 // `args.next()` inside the loop.
557 while let Some(arg) = args.next() {
557 while let Some(arg) = args.next() {
558 if arg == b"--config" {
558 if arg == b"--config" {
559 if let Some(value) = args.next() {
559 if let Some(value) = args.next() {
560 config.push(value)
560 config.push(value)
561 }
561 }
562 } else if let Some(value) = arg.drop_prefix(b"--config=") {
562 } else if let Some(value) = arg.drop_prefix(b"--config=") {
563 config.push(value.to_owned())
563 config.push(value.to_owned())
564 }
564 }
565
565
566 if arg == b"--color" {
566 if arg == b"--color" {
567 if let Some(value) = args.next() {
567 if let Some(value) = args.next() {
568 color = Some(value)
568 color = Some(value)
569 }
569 }
570 } else if let Some(value) = arg.drop_prefix(b"--color=") {
570 } else if let Some(value) = arg.drop_prefix(b"--color=") {
571 color = Some(value.to_owned())
571 color = Some(value.to_owned())
572 }
572 }
573
573
574 if arg == b"--cwd" {
574 if arg == b"--cwd" {
575 if let Some(value) = args.next() {
575 if let Some(value) = args.next() {
576 cwd = Some(value)
576 cwd = Some(value)
577 }
577 }
578 } else if let Some(value) = arg.drop_prefix(b"--cwd=") {
578 } else if let Some(value) = arg.drop_prefix(b"--cwd=") {
579 cwd = Some(value.to_owned())
579 cwd = Some(value.to_owned())
580 }
580 }
581
581
582 if arg == b"--repository" || arg == b"-R" {
582 if arg == b"--repository" || arg == b"-R" {
583 if let Some(value) = args.next() {
583 if let Some(value) = args.next() {
584 repo = Some(value)
584 repo = Some(value)
585 }
585 }
586 } else if let Some(value) = arg.drop_prefix(b"--repository=") {
586 } else if let Some(value) = arg.drop_prefix(b"--repository=") {
587 repo = Some(value.to_owned())
587 repo = Some(value.to_owned())
588 } else if let Some(value) = arg.drop_prefix(b"-R") {
588 } else if let Some(value) = arg.drop_prefix(b"-R") {
589 repo = Some(value.to_owned())
589 repo = Some(value.to_owned())
590 }
590 }
591 }
591 }
592 Self {
592 Self {
593 config,
593 config,
594 color,
594 color,
595 repo,
595 repo,
596 cwd,
596 cwd,
597 }
597 }
598 }
598 }
599 }
599 }
600
600
601 /// What to do when encountering some unsupported feature.
601 /// What to do when encountering some unsupported feature.
602 ///
602 ///
603 /// See `HgError::UnsupportedFeature` and `CommandError::UnsupportedFeature`.
603 /// See `HgError::UnsupportedFeature` and `CommandError::UnsupportedFeature`.
604 enum OnUnsupported {
604 enum OnUnsupported {
605 /// Print an error message describing what feature is not supported,
605 /// Print an error message describing what feature is not supported,
606 /// and exit with code 252.
606 /// and exit with code 252.
607 Abort,
607 Abort,
608 /// Silently exit with code 252.
608 /// Silently exit with code 252.
609 AbortSilent,
609 AbortSilent,
610 /// Try running a Python implementation
610 /// Try running a Python implementation
611 Fallback { executable: Option<Vec<u8>> },
611 Fallback { executable: Option<Vec<u8>> },
612 }
612 }
613
613
614 impl OnUnsupported {
614 impl OnUnsupported {
615 const DEFAULT: Self = OnUnsupported::Abort;
615 const DEFAULT: Self = OnUnsupported::Abort;
616
616
617 fn from_config(config: &Config) -> Self {
617 fn from_config(config: &Config) -> Self {
618 match config
618 match config
619 .get(b"rhg", b"on-unsupported")
619 .get(b"rhg", b"on-unsupported")
620 .map(|value| value.to_ascii_lowercase())
620 .map(|value| value.to_ascii_lowercase())
621 .as_deref()
621 .as_deref()
622 {
622 {
623 Some(b"abort") => OnUnsupported::Abort,
623 Some(b"abort") => OnUnsupported::Abort,
624 Some(b"abort-silent") => OnUnsupported::AbortSilent,
624 Some(b"abort-silent") => OnUnsupported::AbortSilent,
625 Some(b"fallback") => OnUnsupported::Fallback {
625 Some(b"fallback") => OnUnsupported::Fallback {
626 executable: config
626 executable: config
627 .get(b"rhg", b"fallback-executable")
627 .get(b"rhg", b"fallback-executable")
628 .map(|x| x.to_owned()),
628 .map(|x| x.to_owned()),
629 },
629 },
630 None => Self::DEFAULT,
630 None => Self::DEFAULT,
631 Some(_) => {
631 Some(_) => {
632 // TODO: warn about unknown config value
632 // TODO: warn about unknown config value
633 Self::DEFAULT
633 Self::DEFAULT
634 }
634 }
635 }
635 }
636 }
636 }
637 }
637 }
638
638
639 /// The `*` extension is an edge-case for config sub-options that apply to all
639 /// The `*` extension is an edge-case for config sub-options that apply to all
640 /// extensions. For now, only `:required` exists, but that may change in the
640 /// extensions. For now, only `:required` exists, but that may change in the
641 /// future.
641 /// future.
642 const SUPPORTED_EXTENSIONS: &[&[u8]] =
642 const SUPPORTED_EXTENSIONS: &[&[u8]] =
643 &[b"blackbox", b"share", b"sparse", b"narrow", b"*"];
643 &[b"blackbox", b"share", b"sparse", b"narrow", b"*"];
644
644
645 fn check_extensions(config: &Config) -> Result<(), CommandError> {
645 fn check_extensions(config: &Config) -> Result<(), CommandError> {
646 if let Some(b"*") = config.get(b"rhg", b"ignored-extensions") {
647 // All extensions are to be ignored, nothing to do here
648 return Ok(());
649 }
650
646 let enabled: HashSet<&[u8]> = config
651 let enabled: HashSet<&[u8]> = config
647 .get_section_keys(b"extensions")
652 .get_section_keys(b"extensions")
648 .into_iter()
653 .into_iter()
649 .map(|extension| {
654 .map(|extension| {
650 // Ignore extension suboptions. Only `required` exists for now.
655 // Ignore extension suboptions. Only `required` exists for now.
651 // `rhg` either supports an extension or doesn't, so it doesn't
656 // `rhg` either supports an extension or doesn't, so it doesn't
652 // make sense to consider the loading of an extension.
657 // make sense to consider the loading of an extension.
653 extension.split_2(b':').unwrap_or((extension, b"")).0
658 extension.split_2(b':').unwrap_or((extension, b"")).0
654 })
659 })
655 .collect();
660 .collect();
656
661
657 let mut unsupported = enabled;
662 let mut unsupported = enabled;
658 for supported in SUPPORTED_EXTENSIONS {
663 for supported in SUPPORTED_EXTENSIONS {
659 unsupported.remove(supported);
664 unsupported.remove(supported);
660 }
665 }
661
666
662 if let Some(ignored_list) = config.get_list(b"rhg", b"ignored-extensions")
667 if let Some(ignored_list) = config.get_list(b"rhg", b"ignored-extensions")
663 {
668 {
664 for ignored in ignored_list {
669 for ignored in ignored_list {
665 unsupported.remove(ignored.as_slice());
670 unsupported.remove(ignored.as_slice());
666 }
671 }
667 }
672 }
668
673
669 if unsupported.is_empty() {
674 if unsupported.is_empty() {
670 Ok(())
675 Ok(())
671 } else {
676 } else {
672 Err(CommandError::UnsupportedFeature {
677 Err(CommandError::UnsupportedFeature {
673 message: format_bytes!(
678 message: format_bytes!(
674 b"extensions: {} (consider adding them to 'rhg.ignored-extensions' config)",
679 b"extensions: {} (consider adding them to 'rhg.ignored-extensions' config)",
675 join(unsupported, b", ")
680 join(unsupported, b", ")
676 ),
681 ),
677 })
682 })
678 }
683 }
679 }
684 }
680
685
681 fn check_unsupported(
686 fn check_unsupported(
682 config: &Config,
687 config: &Config,
683 repo: Result<&Repo, &NoRepoInCwdError>,
688 repo: Result<&Repo, &NoRepoInCwdError>,
684 ) -> Result<(), CommandError> {
689 ) -> Result<(), CommandError> {
685 check_extensions(config)?;
690 check_extensions(config)?;
686
691
687 if std::env::var_os("HG_PENDING").is_some() {
692 if std::env::var_os("HG_PENDING").is_some() {
688 // TODO: only if the value is `== repo.working_directory`?
693 // TODO: only if the value is `== repo.working_directory`?
689 // What about relative v.s. absolute paths?
694 // What about relative v.s. absolute paths?
690 Err(CommandError::unsupported("$HG_PENDING"))?
695 Err(CommandError::unsupported("$HG_PENDING"))?
691 }
696 }
692
697
693 if let Ok(repo) = repo {
698 if let Ok(repo) = repo {
694 if repo.has_subrepos()? {
699 if repo.has_subrepos()? {
695 Err(CommandError::unsupported("sub-repositories"))?
700 Err(CommandError::unsupported("sub-repositories"))?
696 }
701 }
697 }
702 }
698
703
699 if config.has_non_empty_section(b"encode") {
704 if config.has_non_empty_section(b"encode") {
700 Err(CommandError::unsupported("[encode] config"))?
705 Err(CommandError::unsupported("[encode] config"))?
701 }
706 }
702
707
703 if config.has_non_empty_section(b"decode") {
708 if config.has_non_empty_section(b"decode") {
704 Err(CommandError::unsupported("[decode] config"))?
709 Err(CommandError::unsupported("[decode] config"))?
705 }
710 }
706
711
707 Ok(())
712 Ok(())
708 }
713 }
@@ -1,393 +1,407 b''
1 #require rhg
1 #require rhg
2
2
3 $ NO_FALLBACK="env RHG_ON_UNSUPPORTED=abort"
3 $ NO_FALLBACK="env RHG_ON_UNSUPPORTED=abort"
4
4
5 Unimplemented command
5 Unimplemented command
6 $ $NO_FALLBACK rhg unimplemented-command
6 $ $NO_FALLBACK rhg unimplemented-command
7 unsupported feature: error: Found argument 'unimplemented-command' which wasn't expected, or isn't valid in this context
7 unsupported feature: error: Found argument 'unimplemented-command' which wasn't expected, or isn't valid in this context
8
8
9 USAGE:
9 USAGE:
10 rhg [OPTIONS] <SUBCOMMAND>
10 rhg [OPTIONS] <SUBCOMMAND>
11
11
12 For more information try --help
12 For more information try --help
13
13
14 [252]
14 [252]
15 $ rhg unimplemented-command --config rhg.on-unsupported=abort-silent
15 $ rhg unimplemented-command --config rhg.on-unsupported=abort-silent
16 [252]
16 [252]
17
17
18 Finding root
18 Finding root
19 $ $NO_FALLBACK rhg root
19 $ $NO_FALLBACK rhg root
20 abort: no repository found in '$TESTTMP' (.hg not found)!
20 abort: no repository found in '$TESTTMP' (.hg not found)!
21 [255]
21 [255]
22
22
23 $ hg init repository
23 $ hg init repository
24 $ cd repository
24 $ cd repository
25 $ $NO_FALLBACK rhg root
25 $ $NO_FALLBACK rhg root
26 $TESTTMP/repository
26 $TESTTMP/repository
27
27
28 Reading and setting configuration
28 Reading and setting configuration
29 $ echo "[ui]" >> $HGRCPATH
29 $ echo "[ui]" >> $HGRCPATH
30 $ echo "username = user1" >> $HGRCPATH
30 $ echo "username = user1" >> $HGRCPATH
31 $ $NO_FALLBACK rhg config ui.username
31 $ $NO_FALLBACK rhg config ui.username
32 user1
32 user1
33 $ echo "[ui]" >> .hg/hgrc
33 $ echo "[ui]" >> .hg/hgrc
34 $ echo "username = user2" >> .hg/hgrc
34 $ echo "username = user2" >> .hg/hgrc
35 $ $NO_FALLBACK rhg config ui.username
35 $ $NO_FALLBACK rhg config ui.username
36 user2
36 user2
37 $ $NO_FALLBACK rhg --config ui.username=user3 config ui.username
37 $ $NO_FALLBACK rhg --config ui.username=user3 config ui.username
38 user3
38 user3
39
39
40 Unwritable file descriptor
40 Unwritable file descriptor
41 $ $NO_FALLBACK rhg root > /dev/full
41 $ $NO_FALLBACK rhg root > /dev/full
42 abort: No space left on device (os error 28)
42 abort: No space left on device (os error 28)
43 [255]
43 [255]
44
44
45 Deleted repository
45 Deleted repository
46 $ rm -rf `pwd`
46 $ rm -rf `pwd`
47 $ $NO_FALLBACK rhg root
47 $ $NO_FALLBACK rhg root
48 abort: error getting current working directory: $ENOENT$
48 abort: error getting current working directory: $ENOENT$
49 [255]
49 [255]
50
50
51 Listing tracked files
51 Listing tracked files
52 $ cd $TESTTMP
52 $ cd $TESTTMP
53 $ hg init repository
53 $ hg init repository
54 $ cd repository
54 $ cd repository
55 $ for i in 1 2 3; do
55 $ for i in 1 2 3; do
56 > echo $i >> file$i
56 > echo $i >> file$i
57 > hg add file$i
57 > hg add file$i
58 > done
58 > done
59 > hg commit -m "commit $i" -q
59 > hg commit -m "commit $i" -q
60
60
61 Listing tracked files from root
61 Listing tracked files from root
62 $ $NO_FALLBACK rhg files
62 $ $NO_FALLBACK rhg files
63 file1
63 file1
64 file2
64 file2
65 file3
65 file3
66
66
67 Listing tracked files from subdirectory
67 Listing tracked files from subdirectory
68 $ mkdir -p path/to/directory
68 $ mkdir -p path/to/directory
69 $ cd path/to/directory
69 $ cd path/to/directory
70 $ $NO_FALLBACK rhg files
70 $ $NO_FALLBACK rhg files
71 ../../../file1
71 ../../../file1
72 ../../../file2
72 ../../../file2
73 ../../../file3
73 ../../../file3
74
74
75 Listing tracked files through broken pipe
75 Listing tracked files through broken pipe
76 $ $NO_FALLBACK rhg files | head -n 1
76 $ $NO_FALLBACK rhg files | head -n 1
77 ../../../file1
77 ../../../file1
78
78
79 Debuging data in inline index
79 Debuging data in inline index
80 $ cd $TESTTMP
80 $ cd $TESTTMP
81 $ rm -rf repository
81 $ rm -rf repository
82 $ hg init repository
82 $ hg init repository
83 $ cd repository
83 $ cd repository
84 $ for i in 1 2 3 4 5 6; do
84 $ for i in 1 2 3 4 5 6; do
85 > echo $i >> file-$i
85 > echo $i >> file-$i
86 > hg add file-$i
86 > hg add file-$i
87 > hg commit -m "Commit $i" -q
87 > hg commit -m "Commit $i" -q
88 > done
88 > done
89 $ $NO_FALLBACK rhg debugdata -c 2
89 $ $NO_FALLBACK rhg debugdata -c 2
90 8d0267cb034247ebfa5ee58ce59e22e57a492297
90 8d0267cb034247ebfa5ee58ce59e22e57a492297
91 test
91 test
92 0 0
92 0 0
93 file-3
93 file-3
94
94
95 Commit 3 (no-eol)
95 Commit 3 (no-eol)
96 $ $NO_FALLBACK rhg debugdata -m 2
96 $ $NO_FALLBACK rhg debugdata -m 2
97 file-1\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
97 file-1\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
98 file-2\x005d9299349fc01ddd25d0070d149b124d8f10411e (esc)
98 file-2\x005d9299349fc01ddd25d0070d149b124d8f10411e (esc)
99 file-3\x002661d26c649684b482d10f91960cc3db683c38b4 (esc)
99 file-3\x002661d26c649684b482d10f91960cc3db683c38b4 (esc)
100
100
101 Debuging with full node id
101 Debuging with full node id
102 $ $NO_FALLBACK rhg debugdata -c `hg log -r 0 -T '{node}'`
102 $ $NO_FALLBACK rhg debugdata -c `hg log -r 0 -T '{node}'`
103 d1d1c679d3053e8926061b6f45ca52009f011e3f
103 d1d1c679d3053e8926061b6f45ca52009f011e3f
104 test
104 test
105 0 0
105 0 0
106 file-1
106 file-1
107
107
108 Commit 1 (no-eol)
108 Commit 1 (no-eol)
109
109
110 Specifying revisions by changeset ID
110 Specifying revisions by changeset ID
111 $ hg log -T '{node}\n'
111 $ hg log -T '{node}\n'
112 c6ad58c44207b6ff8a4fbbca7045a5edaa7e908b
112 c6ad58c44207b6ff8a4fbbca7045a5edaa7e908b
113 d654274993d0149eecc3cc03214f598320211900
113 d654274993d0149eecc3cc03214f598320211900
114 f646af7e96481d3a5470b695cf30ad8e3ab6c575
114 f646af7e96481d3a5470b695cf30ad8e3ab6c575
115 cf8b83f14ead62b374b6e91a0e9303b85dfd9ed7
115 cf8b83f14ead62b374b6e91a0e9303b85dfd9ed7
116 91c6f6e73e39318534dc415ea4e8a09c99cd74d6
116 91c6f6e73e39318534dc415ea4e8a09c99cd74d6
117 6ae9681c6d30389694d8701faf24b583cf3ccafe
117 6ae9681c6d30389694d8701faf24b583cf3ccafe
118 $ $NO_FALLBACK rhg files -r cf8b83
118 $ $NO_FALLBACK rhg files -r cf8b83
119 file-1
119 file-1
120 file-2
120 file-2
121 file-3
121 file-3
122 $ $NO_FALLBACK rhg cat -r cf8b83 file-2
122 $ $NO_FALLBACK rhg cat -r cf8b83 file-2
123 2
123 2
124 $ $NO_FALLBACK rhg cat --rev cf8b83 file-2
124 $ $NO_FALLBACK rhg cat --rev cf8b83 file-2
125 2
125 2
126 $ $NO_FALLBACK rhg cat -r c file-2
126 $ $NO_FALLBACK rhg cat -r c file-2
127 abort: ambiguous revision identifier: c
127 abort: ambiguous revision identifier: c
128 [255]
128 [255]
129 $ $NO_FALLBACK rhg cat -r d file-2
129 $ $NO_FALLBACK rhg cat -r d file-2
130 2
130 2
131 $ $NO_FALLBACK rhg cat -r 0000 file-2
131 $ $NO_FALLBACK rhg cat -r 0000 file-2
132 file-2: no such file in rev 000000000000
132 file-2: no such file in rev 000000000000
133 [1]
133 [1]
134
134
135 Cat files
135 Cat files
136 $ cd $TESTTMP
136 $ cd $TESTTMP
137 $ rm -rf repository
137 $ rm -rf repository
138 $ hg init repository
138 $ hg init repository
139 $ cd repository
139 $ cd repository
140 $ echo "original content" > original
140 $ echo "original content" > original
141 $ hg add original
141 $ hg add original
142 $ hg commit -m "add original" original
142 $ hg commit -m "add original" original
143 Without `--rev`
143 Without `--rev`
144 $ $NO_FALLBACK rhg cat original
144 $ $NO_FALLBACK rhg cat original
145 original content
145 original content
146 With `--rev`
146 With `--rev`
147 $ $NO_FALLBACK rhg cat -r 0 original
147 $ $NO_FALLBACK rhg cat -r 0 original
148 original content
148 original content
149 Cat copied file should not display copy metadata
149 Cat copied file should not display copy metadata
150 $ hg copy original copy_of_original
150 $ hg copy original copy_of_original
151 $ hg commit -m "add copy of original"
151 $ hg commit -m "add copy of original"
152 $ $NO_FALLBACK rhg cat original
152 $ $NO_FALLBACK rhg cat original
153 original content
153 original content
154 $ $NO_FALLBACK rhg cat -r 1 copy_of_original
154 $ $NO_FALLBACK rhg cat -r 1 copy_of_original
155 original content
155 original content
156
156
157
157
158 Fallback to Python
158 Fallback to Python
159 $ $NO_FALLBACK rhg cat original --exclude="*.rs"
159 $ $NO_FALLBACK rhg cat original --exclude="*.rs"
160 unsupported feature: error: Found argument '--exclude' which wasn't expected, or isn't valid in this context
160 unsupported feature: error: Found argument '--exclude' which wasn't expected, or isn't valid in this context
161
161
162 USAGE:
162 USAGE:
163 rhg cat [OPTIONS] <FILE>...
163 rhg cat [OPTIONS] <FILE>...
164
164
165 For more information try --help
165 For more information try --help
166
166
167 [252]
167 [252]
168 $ rhg cat original --exclude="*.rs"
168 $ rhg cat original --exclude="*.rs"
169 original content
169 original content
170
170
171 $ (unset RHG_FALLBACK_EXECUTABLE; rhg cat original --exclude="*.rs")
171 $ (unset RHG_FALLBACK_EXECUTABLE; rhg cat original --exclude="*.rs")
172 abort: 'rhg.on-unsupported=fallback' without 'rhg.fallback-executable' set.
172 abort: 'rhg.on-unsupported=fallback' without 'rhg.fallback-executable' set.
173 [255]
173 [255]
174
174
175 $ (unset RHG_FALLBACK_EXECUTABLE; rhg cat original)
175 $ (unset RHG_FALLBACK_EXECUTABLE; rhg cat original)
176 original content
176 original content
177
177
178 $ rhg cat original --exclude="*.rs" --config rhg.fallback-executable=false
178 $ rhg cat original --exclude="*.rs" --config rhg.fallback-executable=false
179 [1]
179 [1]
180
180
181 $ rhg cat original --exclude="*.rs" --config rhg.fallback-executable=hg-non-existent
181 $ rhg cat original --exclude="*.rs" --config rhg.fallback-executable=hg-non-existent
182 tried to fall back to a 'hg-non-existent' sub-process but got error $ENOENT$
182 tried to fall back to a 'hg-non-existent' sub-process but got error $ENOENT$
183 unsupported feature: error: Found argument '--exclude' which wasn't expected, or isn't valid in this context
183 unsupported feature: error: Found argument '--exclude' which wasn't expected, or isn't valid in this context
184
184
185 USAGE:
185 USAGE:
186 rhg cat [OPTIONS] <FILE>...
186 rhg cat [OPTIONS] <FILE>...
187
187
188 For more information try --help
188 For more information try --help
189
189
190 [252]
190 [252]
191
191
192 $ rhg cat original --exclude="*.rs" --config rhg.fallback-executable=rhg
192 $ rhg cat original --exclude="*.rs" --config rhg.fallback-executable=rhg
193 Blocking recursive fallback. The 'rhg.fallback-executable = rhg' config points to `rhg` itself.
193 Blocking recursive fallback. The 'rhg.fallback-executable = rhg' config points to `rhg` itself.
194 unsupported feature: error: Found argument '--exclude' which wasn't expected, or isn't valid in this context
194 unsupported feature: error: Found argument '--exclude' which wasn't expected, or isn't valid in this context
195
195
196 USAGE:
196 USAGE:
197 rhg cat [OPTIONS] <FILE>...
197 rhg cat [OPTIONS] <FILE>...
198
198
199 For more information try --help
199 For more information try --help
200
200
201 [252]
201 [252]
202
202
203 Fallback with shell path segments
203 Fallback with shell path segments
204 $ $NO_FALLBACK rhg cat .
204 $ $NO_FALLBACK rhg cat .
205 unsupported feature: `..` or `.` path segment
205 unsupported feature: `..` or `.` path segment
206 [252]
206 [252]
207 $ $NO_FALLBACK rhg cat ..
207 $ $NO_FALLBACK rhg cat ..
208 unsupported feature: `..` or `.` path segment
208 unsupported feature: `..` or `.` path segment
209 [252]
209 [252]
210 $ $NO_FALLBACK rhg cat ../..
210 $ $NO_FALLBACK rhg cat ../..
211 unsupported feature: `..` or `.` path segment
211 unsupported feature: `..` or `.` path segment
212 [252]
212 [252]
213
213
214 Fallback with filesets
214 Fallback with filesets
215 $ $NO_FALLBACK rhg cat "set:c or b"
215 $ $NO_FALLBACK rhg cat "set:c or b"
216 unsupported feature: fileset
216 unsupported feature: fileset
217 [252]
217 [252]
218
218
219 Fallback with generic hooks
219 Fallback with generic hooks
220 $ $NO_FALLBACK rhg cat original --config hooks.pre-cat=something
220 $ $NO_FALLBACK rhg cat original --config hooks.pre-cat=something
221 unsupported feature: pre-cat hook defined
221 unsupported feature: pre-cat hook defined
222 [252]
222 [252]
223
223
224 $ $NO_FALLBACK rhg cat original --config hooks.post-cat=something
224 $ $NO_FALLBACK rhg cat original --config hooks.post-cat=something
225 unsupported feature: post-cat hook defined
225 unsupported feature: post-cat hook defined
226 [252]
226 [252]
227
227
228 $ $NO_FALLBACK rhg cat original --config hooks.fail-cat=something
228 $ $NO_FALLBACK rhg cat original --config hooks.fail-cat=something
229 unsupported feature: fail-cat hook defined
229 unsupported feature: fail-cat hook defined
230 [252]
230 [252]
231
231
232 Fallback with [defaults]
232 Fallback with [defaults]
233 $ $NO_FALLBACK rhg cat original --config "defaults.cat=-r null"
233 $ $NO_FALLBACK rhg cat original --config "defaults.cat=-r null"
234 unsupported feature: `defaults` config set
234 unsupported feature: `defaults` config set
235 [252]
235 [252]
236
236
237
237
238 Requirements
238 Requirements
239 $ $NO_FALLBACK rhg debugrequirements
239 $ $NO_FALLBACK rhg debugrequirements
240 dotencode
240 dotencode
241 fncache
241 fncache
242 generaldelta
242 generaldelta
243 persistent-nodemap
243 persistent-nodemap
244 revlog-compression-zstd (zstd !)
244 revlog-compression-zstd (zstd !)
245 revlogv1
245 revlogv1
246 share-safe
246 share-safe
247 sparserevlog
247 sparserevlog
248 store
248 store
249
249
250 $ echo indoor-pool >> .hg/requires
250 $ echo indoor-pool >> .hg/requires
251 $ $NO_FALLBACK rhg files
251 $ $NO_FALLBACK rhg files
252 unsupported feature: repository requires feature unknown to this Mercurial: indoor-pool
252 unsupported feature: repository requires feature unknown to this Mercurial: indoor-pool
253 [252]
253 [252]
254
254
255 $ $NO_FALLBACK rhg cat -r 1 copy_of_original
255 $ $NO_FALLBACK rhg cat -r 1 copy_of_original
256 unsupported feature: repository requires feature unknown to this Mercurial: indoor-pool
256 unsupported feature: repository requires feature unknown to this Mercurial: indoor-pool
257 [252]
257 [252]
258
258
259 $ $NO_FALLBACK rhg debugrequirements
259 $ $NO_FALLBACK rhg debugrequirements
260 unsupported feature: repository requires feature unknown to this Mercurial: indoor-pool
260 unsupported feature: repository requires feature unknown to this Mercurial: indoor-pool
261 [252]
261 [252]
262
262
263 $ echo -e '\xFF' >> .hg/requires
263 $ echo -e '\xFF' >> .hg/requires
264 $ $NO_FALLBACK rhg debugrequirements
264 $ $NO_FALLBACK rhg debugrequirements
265 abort: parse error in 'requires' file
265 abort: parse error in 'requires' file
266 [255]
266 [255]
267
267
268 Persistent nodemap
268 Persistent nodemap
269 $ cd $TESTTMP
269 $ cd $TESTTMP
270 $ rm -rf repository
270 $ rm -rf repository
271 $ hg --config format.use-persistent-nodemap=no init repository
271 $ hg --config format.use-persistent-nodemap=no init repository
272 $ cd repository
272 $ cd repository
273 $ $NO_FALLBACK rhg debugrequirements | grep nodemap
273 $ $NO_FALLBACK rhg debugrequirements | grep nodemap
274 [1]
274 [1]
275 $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn"
275 $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn"
276 $ hg id -r tip
276 $ hg id -r tip
277 c3ae8dec9fad tip
277 c3ae8dec9fad tip
278 $ ls .hg/store/00changelog*
278 $ ls .hg/store/00changelog*
279 .hg/store/00changelog.d
279 .hg/store/00changelog.d
280 .hg/store/00changelog.i
280 .hg/store/00changelog.i
281 $ $NO_FALLBACK rhg files -r c3ae8dec9fad
281 $ $NO_FALLBACK rhg files -r c3ae8dec9fad
282 of
282 of
283
283
284 $ cd $TESTTMP
284 $ cd $TESTTMP
285 $ rm -rf repository
285 $ rm -rf repository
286 $ hg --config format.use-persistent-nodemap=True init repository
286 $ hg --config format.use-persistent-nodemap=True init repository
287 $ cd repository
287 $ cd repository
288 $ $NO_FALLBACK rhg debugrequirements | grep nodemap
288 $ $NO_FALLBACK rhg debugrequirements | grep nodemap
289 persistent-nodemap
289 persistent-nodemap
290 $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn"
290 $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn"
291 $ hg id -r tip
291 $ hg id -r tip
292 c3ae8dec9fad tip
292 c3ae8dec9fad tip
293 $ ls .hg/store/00changelog*
293 $ ls .hg/store/00changelog*
294 .hg/store/00changelog-*.nd (glob)
294 .hg/store/00changelog-*.nd (glob)
295 .hg/store/00changelog.d
295 .hg/store/00changelog.d
296 .hg/store/00changelog.i
296 .hg/store/00changelog.i
297 .hg/store/00changelog.n
297 .hg/store/00changelog.n
298
298
299 Specifying revisions by changeset ID
299 Specifying revisions by changeset ID
300 $ $NO_FALLBACK rhg files -r c3ae8dec9fad
300 $ $NO_FALLBACK rhg files -r c3ae8dec9fad
301 of
301 of
302 $ $NO_FALLBACK rhg cat -r c3ae8dec9fad of
302 $ $NO_FALLBACK rhg cat -r c3ae8dec9fad of
303 r5000
303 r5000
304
304
305 Crate a shared repository
305 Crate a shared repository
306
306
307 $ echo "[extensions]" >> $HGRCPATH
307 $ echo "[extensions]" >> $HGRCPATH
308 $ echo "share = " >> $HGRCPATH
308 $ echo "share = " >> $HGRCPATH
309
309
310 $ cd $TESTTMP
310 $ cd $TESTTMP
311 $ hg init repo1
311 $ hg init repo1
312 $ echo a > repo1/a
312 $ echo a > repo1/a
313 $ hg -R repo1 commit -A -m'init'
313 $ hg -R repo1 commit -A -m'init'
314 adding a
314 adding a
315
315
316 $ hg share repo1 repo2
316 $ hg share repo1 repo2
317 updating working directory
317 updating working directory
318 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
318 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
319
319
320 And check that basic rhg commands work with sharing
320 And check that basic rhg commands work with sharing
321
321
322 $ $NO_FALLBACK rhg files -R repo2
322 $ $NO_FALLBACK rhg files -R repo2
323 repo2/a
323 repo2/a
324 $ $NO_FALLBACK rhg -R repo2 cat -r 0 repo2/a
324 $ $NO_FALLBACK rhg -R repo2 cat -r 0 repo2/a
325 a
325 a
326
326
327 Same with relative sharing
327 Same with relative sharing
328
328
329 $ hg share repo2 repo3 --relative
329 $ hg share repo2 repo3 --relative
330 updating working directory
330 updating working directory
331 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
331 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
332
332
333 $ $NO_FALLBACK rhg files -R repo3
333 $ $NO_FALLBACK rhg files -R repo3
334 repo3/a
334 repo3/a
335 $ $NO_FALLBACK rhg -R repo3 cat -r 0 repo3/a
335 $ $NO_FALLBACK rhg -R repo3 cat -r 0 repo3/a
336 a
336 a
337
337
338 Same with share-safe
338 Same with share-safe
339
339
340 $ echo "[format]" >> $HGRCPATH
340 $ echo "[format]" >> $HGRCPATH
341 $ echo "use-share-safe = True" >> $HGRCPATH
341 $ echo "use-share-safe = True" >> $HGRCPATH
342
342
343 $ cd $TESTTMP
343 $ cd $TESTTMP
344 $ hg init repo4
344 $ hg init repo4
345 $ cd repo4
345 $ cd repo4
346 $ echo a > a
346 $ echo a > a
347 $ hg commit -A -m'init'
347 $ hg commit -A -m'init'
348 adding a
348 adding a
349
349
350 $ cd ..
350 $ cd ..
351 $ hg share repo4 repo5
351 $ hg share repo4 repo5
352 updating working directory
352 updating working directory
353 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
353 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
354
354
355 And check that basic rhg commands work with sharing
355 And check that basic rhg commands work with sharing
356
356
357 $ cd repo5
357 $ cd repo5
358 $ $NO_FALLBACK rhg files
358 $ $NO_FALLBACK rhg files
359 a
359 a
360 $ $NO_FALLBACK rhg cat -r 0 a
360 $ $NO_FALLBACK rhg cat -r 0 a
361 a
361 a
362
362
363 The blackbox extension is supported
363 The blackbox extension is supported
364
364
365 $ echo "[extensions]" >> $HGRCPATH
365 $ echo "[extensions]" >> $HGRCPATH
366 $ echo "blackbox =" >> $HGRCPATH
366 $ echo "blackbox =" >> $HGRCPATH
367 $ echo "[blackbox]" >> $HGRCPATH
367 $ echo "[blackbox]" >> $HGRCPATH
368 $ echo "maxsize = 1" >> $HGRCPATH
368 $ echo "maxsize = 1" >> $HGRCPATH
369 $ $NO_FALLBACK rhg files > /dev/null
369 $ $NO_FALLBACK rhg files > /dev/null
370 $ cat .hg/blackbox.log
370 $ cat .hg/blackbox.log
371 ????-??-?? ??:??:??.??? * @d3873e73d99ef67873dac33fbcc66268d5d2b6f4 (*)> (rust) files exited 0 after 0.??? seconds (glob)
371 ????-??-?? ??:??:??.??? * @d3873e73d99ef67873dac33fbcc66268d5d2b6f4 (*)> (rust) files exited 0 after 0.??? seconds (glob)
372 $ cat .hg/blackbox.log.1
372 $ cat .hg/blackbox.log.1
373 ????-??-?? ??:??:??.??? * @d3873e73d99ef67873dac33fbcc66268d5d2b6f4 (*)> (rust) files (glob)
373 ????-??-?? ??:??:??.??? * @d3873e73d99ef67873dac33fbcc66268d5d2b6f4 (*)> (rust) files (glob)
374
374
375 Subrepos are not supported
375 Subrepos are not supported
376
376
377 $ touch .hgsub
377 $ touch .hgsub
378 $ $NO_FALLBACK rhg files
378 $ $NO_FALLBACK rhg files
379 unsupported feature: subrepos (.hgsub is present)
379 unsupported feature: subrepos (.hgsub is present)
380 [252]
380 [252]
381 $ rhg files
381 $ rhg files
382 a
382 a
383 $ rm .hgsub
383 $ rm .hgsub
384
384
385 The `:required` extension suboptions are correctly ignored
385 The `:required` extension suboptions are correctly ignored
386
386
387 $ echo "[extensions]" >> $HGRCPATH
387 $ echo "[extensions]" >> $HGRCPATH
388 $ echo "blackbox:required = yes" >> $HGRCPATH
388 $ echo "blackbox:required = yes" >> $HGRCPATH
389 $ rhg files
389 $ rhg files
390 a
390 a
391 $ echo "*:required = yes" >> $HGRCPATH
391 $ echo "*:required = yes" >> $HGRCPATH
392 $ rhg files
392 $ rhg files
393 a
393 a
394
395 We can ignore all extensions at once
396
397 $ echo "[extensions]" >> $HGRCPATH
398 $ echo "thisextensionbetternotexist=" >> $HGRCPATH
399 $ echo "thisextensionbetternotexisteither=" >> $HGRCPATH
400 $ $NO_FALLBACK rhg files
401 unsupported feature: extensions: thisextensionbetternotexist, thisextensionbetternotexisteither (consider adding them to 'rhg.ignored-extensions' config)
402 [252]
403
404 $ echo "[rhg]" >> $HGRCPATH
405 $ echo "ignored-extensions=*" >> $HGRCPATH
406 $ $NO_FALLBACK rhg files
407 a
General Comments 0
You need to be logged in to leave comments. Login now