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