2 use clap::Arg; |
2 use clap::Arg; |
3 use format_bytes::format_bytes; |
3 use format_bytes::format_bytes; |
4 use hg::operations::cat; |
4 use hg::operations::cat; |
5 use hg::utils::hg_path::HgPathBuf; |
5 use hg::utils::hg_path::HgPathBuf; |
6 use micro_timer::timed; |
6 use micro_timer::timed; |
|
7 use std::ffi::OsString; |
|
8 use std::os::unix::prelude::OsStrExt; |
7 |
9 |
8 pub const HELP_TEXT: &str = " |
10 pub const HELP_TEXT: &str = " |
9 Output the current or given revision of files |
11 Output the current or given revision of files |
10 "; |
12 "; |
11 |
13 |
12 pub fn args() -> clap::App<'static, 'static> { |
14 pub fn args() -> clap::Command { |
13 clap::SubCommand::with_name("cat") |
15 clap::command!("cat") |
14 .arg( |
16 .arg( |
15 Arg::with_name("rev") |
17 Arg::new("rev") |
16 .help("search the repository as it is in REV") |
18 .help("search the repository as it is in REV") |
17 .short("-r") |
19 .short('r') |
18 .long("--rev") |
20 .long("rev") |
19 .value_name("REV") |
21 .value_name("REV"), |
20 .takes_value(true), |
|
21 ) |
22 ) |
22 .arg( |
23 .arg( |
23 clap::Arg::with_name("files") |
24 clap::Arg::new("files") |
24 .required(true) |
25 .required(true) |
25 .multiple(true) |
26 .num_args(1..) |
26 .empty_values(false) |
|
27 .value_name("FILE") |
27 .value_name("FILE") |
|
28 .value_parser(clap::value_parser!(std::ffi::OsString)) |
28 .help("Files to output"), |
29 .help("Files to output"), |
29 ) |
30 ) |
30 .about(HELP_TEXT) |
31 .about(HELP_TEXT) |
31 } |
32 } |
32 |
33 |
39 "cat is disabled in rhg (enable it with 'rhg.cat = true' \ |
40 "cat is disabled in rhg (enable it with 'rhg.cat = true' \ |
40 or enable fallback with 'rhg.on-unsupported = fallback')", |
41 or enable fallback with 'rhg.on-unsupported = fallback')", |
41 )); |
42 )); |
42 } |
43 } |
43 |
44 |
44 let rev = invocation.subcommand_args.value_of("rev"); |
45 let rev = invocation.subcommand_args.get_one::<String>("rev"); |
45 let file_args = match invocation.subcommand_args.values_of("files") { |
46 let file_args = |
46 Some(files) => files.collect(), |
47 match invocation.subcommand_args.get_many::<OsString>("files") { |
47 None => vec![], |
48 Some(files) => files |
48 }; |
49 .filter(|s| !s.is_empty()) |
|
50 .map(|s| s.as_os_str()) |
|
51 .collect(), |
|
52 None => vec![], |
|
53 }; |
49 |
54 |
50 let repo = invocation.repo?; |
55 let repo = invocation.repo?; |
51 let cwd = hg::utils::current_dir()?; |
56 let cwd = hg::utils::current_dir()?; |
52 let working_directory = repo.working_directory_path(); |
57 let working_directory = repo.working_directory_path(); |
53 let working_directory = cwd.join(working_directory); // Make it absolute |
58 let working_directory = cwd.join(working_directory); // Make it absolute |
54 |
59 |
55 let mut files = vec![]; |
60 let mut files = vec![]; |
56 for file in file_args.iter() { |
61 for file in file_args { |
57 if file.starts_with("set:") { |
62 if file.as_bytes().starts_with(b"set:") { |
58 let message = "fileset"; |
63 let message = "fileset"; |
59 return Err(CommandError::unsupported(message)); |
64 return Err(CommandError::unsupported(message)); |
60 } |
65 } |
61 |
66 |
62 let normalized = cwd.join(&file); |
67 let normalized = cwd.join(&file); |
63 // TODO: actually normalize `..` path segments etc? |
68 // TODO: actually normalize `..` path segments etc? |
64 let dotted = normalized.components().any(|c| c.as_os_str() == ".."); |
69 let dotted = normalized.components().any(|c| c.as_os_str() == ".."); |
65 if file == &"." || dotted { |
70 if file.as_bytes() == b"." || dotted { |
66 let message = "`..` or `.` path segment"; |
71 let message = "`..` or `.` path segment"; |
67 return Err(CommandError::unsupported(message)); |
72 return Err(CommandError::unsupported(message)); |
68 } |
73 } |
69 let relative_path = working_directory |
74 let relative_path = working_directory |
70 .strip_prefix(&cwd) |
75 .strip_prefix(&cwd) |
72 let stripped = normalized |
77 let stripped = normalized |
73 .strip_prefix(&working_directory) |
78 .strip_prefix(&working_directory) |
74 .map_err(|_| { |
79 .map_err(|_| { |
75 CommandError::abort(format!( |
80 CommandError::abort(format!( |
76 "abort: {} not under root '{}'\n(consider using '--cwd {}')", |
81 "abort: {} not under root '{}'\n(consider using '--cwd {}')", |
77 file, |
82 String::from_utf8_lossy(file.as_bytes()), |
78 working_directory.display(), |
83 working_directory.display(), |
79 relative_path.display(), |
84 relative_path.display(), |
80 )) |
85 )) |
81 })?; |
86 })?; |
82 let hg_file = HgPathBuf::try_from(stripped.to_path_buf()) |
87 let hg_file = HgPathBuf::try_from(stripped.to_path_buf()) |