Show More
@@ -1,71 +1,80 b'' | |||||
1 | // list_tracked_files.rs |
|
1 | // list_tracked_files.rs | |
2 | // |
|
2 | // | |
3 | // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net> |
|
3 | // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net> | |
4 | // |
|
4 | // | |
5 | // This software may be used and distributed according to the terms of the |
|
5 | // This software may be used and distributed according to the terms of the | |
6 | // GNU General Public License version 2 or any later version. |
|
6 | // GNU General Public License version 2 or any later version. | |
7 |
|
7 | |||
8 | use crate::repo::Repo; |
|
8 | use crate::repo::Repo; | |
9 | use crate::revlog::revlog::RevlogError; |
|
9 | use crate::revlog::revlog::RevlogError; | |
10 | use crate::revlog::Node; |
|
10 | use crate::revlog::Node; | |
11 |
|
11 | |||
12 | use crate::utils::hg_path::HgPathBuf; |
|
12 | use crate::utils::hg_path::HgPathBuf; | |
13 |
|
13 | |||
|
14 | use itertools::EitherOrBoth::{Both, Left, Right}; | |||
|
15 | use itertools::Itertools; | |||
|
16 | ||||
14 | pub struct CatOutput { |
|
17 | pub struct CatOutput { | |
15 | /// Whether any file in the manifest matched the paths given as CLI |
|
18 | /// Whether any file in the manifest matched the paths given as CLI | |
16 | /// arguments |
|
19 | /// arguments | |
17 | pub found_any: bool, |
|
20 | pub found_any: bool, | |
18 | /// The contents of matching files, in manifest order |
|
21 | /// The contents of matching files, in manifest order | |
19 | pub concatenated: Vec<u8>, |
|
22 | pub concatenated: Vec<u8>, | |
20 | /// Which of the CLI arguments did not match any manifest file |
|
23 | /// Which of the CLI arguments did not match any manifest file | |
21 | pub missing: Vec<HgPathBuf>, |
|
24 | pub missing: Vec<HgPathBuf>, | |
22 | /// The node ID that the given revset was resolved to |
|
25 | /// The node ID that the given revset was resolved to | |
23 | pub node: Node, |
|
26 | pub node: Node, | |
24 | } |
|
27 | } | |
25 |
|
28 | |||
26 | /// Output the given revision of files |
|
29 | /// Output the given revision of files | |
27 | /// |
|
30 | /// | |
28 | /// * `root`: Repository root |
|
31 | /// * `root`: Repository root | |
29 | /// * `rev`: The revision to cat the files from. |
|
32 | /// * `rev`: The revision to cat the files from. | |
30 | /// * `files`: The files to output. |
|
33 | /// * `files`: The files to output. | |
31 | pub fn cat<'a>( |
|
34 | pub fn cat<'a>( | |
32 | repo: &Repo, |
|
35 | repo: &Repo, | |
33 | revset: &str, |
|
36 | revset: &str, | |
34 |
files: |
|
37 | mut files: Vec<HgPathBuf>, | |
35 | ) -> Result<CatOutput, RevlogError> { |
|
38 | ) -> Result<CatOutput, RevlogError> { | |
36 | let rev = crate::revset::resolve_single(revset, repo)?; |
|
39 | let rev = crate::revset::resolve_single(revset, repo)?; | |
37 | let manifest = repo.manifest_for_rev(rev)?; |
|
40 | let manifest = repo.manifest_for_rev(rev)?; | |
38 | let node = *repo |
|
41 | let node = *repo | |
39 | .changelog()? |
|
42 | .changelog()? | |
40 | .node_from_rev(rev) |
|
43 | .node_from_rev(rev) | |
41 | .expect("should succeed when repo.manifest did"); |
|
44 | .expect("should succeed when repo.manifest did"); | |
42 | let mut bytes = vec![]; |
|
45 | let mut bytes = vec![]; | |
43 | let mut matched = vec![false; files.len()]; |
|
|||
44 | let mut found_any = false; |
|
46 | let mut found_any = false; | |
|
47 | files.sort_unstable(); | |||
|
48 | ||||
|
49 | let mut missing = vec![]; | |||
45 |
|
50 | |||
46 | for (manifest_file, node_bytes) in manifest.files_with_nodes() { |
|
51 | for entry in manifest | |
47 | for (cat_file, is_matched) in files.iter().zip(&mut matched) { |
|
52 | .files_with_nodes() | |
48 | if cat_file.as_bytes() == manifest_file.as_bytes() { |
|
53 | .merge_join_by(files.iter(), |(manifest_file, _), file| { | |
49 | *is_matched = true; |
|
54 | manifest_file.cmp(&file.as_ref()) | |
|
55 | }) | |||
|
56 | { | |||
|
57 | match entry { | |||
|
58 | Left(_) => (), | |||
|
59 | Right(path) => missing.push(path), | |||
|
60 | Both((manifest_file, node_bytes), _) => { | |||
50 | found_any = true; |
|
61 | found_any = true; | |
51 | let file_log = repo.filelog(manifest_file)?; |
|
62 | let file_log = repo.filelog(manifest_file)?; | |
52 | let file_node = Node::from_hex_for_repo(node_bytes)?; |
|
63 | let file_node = Node::from_hex_for_repo(node_bytes)?; | |
53 | let entry = file_log.data_for_node(file_node)?; |
|
64 | let entry = file_log.data_for_node(file_node)?; | |
54 | bytes.extend(entry.data()?) |
|
65 | bytes.extend(entry.data()?) | |
55 | } |
|
66 | } | |
56 | } |
|
67 | } | |
57 | } |
|
68 | } | |
58 |
|
69 | |||
59 |
let missing: Vec< |
|
70 | let missing: Vec<HgPathBuf> = missing | |
60 | .iter() |
|
71 | .iter() | |
61 | .zip(&matched) |
|
72 | .map(|file| (*(file.as_ref())).to_owned()) | |
62 | .filter(|pair| !*pair.1) |
|
|||
63 | .map(|pair| pair.0.clone()) |
|
|||
64 | .collect(); |
|
73 | .collect(); | |
65 | Ok(CatOutput { |
|
74 | Ok(CatOutput { | |
66 | found_any, |
|
75 | found_any, | |
67 | concatenated: bytes, |
|
76 | concatenated: bytes, | |
68 | missing, |
|
77 | missing, | |
69 | node, |
|
78 | node, | |
70 | }) |
|
79 | }) | |
71 | } |
|
80 | } |
@@ -1,93 +1,93 b'' | |||||
1 | use crate::error::CommandError; |
|
1 | use crate::error::CommandError; | |
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::convert::TryFrom; |
|
7 | use std::convert::TryFrom; | |
8 |
|
8 | |||
9 | pub const HELP_TEXT: &str = " |
|
9 | pub const HELP_TEXT: &str = " | |
10 | Output the current or given revision of files |
|
10 | Output the current or given revision of files | |
11 | "; |
|
11 | "; | |
12 |
|
12 | |||
13 | pub fn args() -> clap::App<'static, 'static> { |
|
13 | pub fn args() -> clap::App<'static, 'static> { | |
14 | clap::SubCommand::with_name("cat") |
|
14 | clap::SubCommand::with_name("cat") | |
15 | .arg( |
|
15 | .arg( | |
16 | Arg::with_name("rev") |
|
16 | Arg::with_name("rev") | |
17 | .help("search the repository as it is in REV") |
|
17 | .help("search the repository as it is in REV") | |
18 | .short("-r") |
|
18 | .short("-r") | |
19 | .long("--rev") |
|
19 | .long("--rev") | |
20 | .value_name("REV") |
|
20 | .value_name("REV") | |
21 | .takes_value(true), |
|
21 | .takes_value(true), | |
22 | ) |
|
22 | ) | |
23 | .arg( |
|
23 | .arg( | |
24 | clap::Arg::with_name("files") |
|
24 | clap::Arg::with_name("files") | |
25 | .required(true) |
|
25 | .required(true) | |
26 | .multiple(true) |
|
26 | .multiple(true) | |
27 | .empty_values(false) |
|
27 | .empty_values(false) | |
28 | .value_name("FILE") |
|
28 | .value_name("FILE") | |
29 | .help("Files to output"), |
|
29 | .help("Files to output"), | |
30 | ) |
|
30 | ) | |
31 | .about(HELP_TEXT) |
|
31 | .about(HELP_TEXT) | |
32 | } |
|
32 | } | |
33 |
|
33 | |||
34 | #[timed] |
|
34 | #[timed] | |
35 | pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> { |
|
35 | pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> { | |
36 | let rev = invocation.subcommand_args.value_of("rev"); |
|
36 | let rev = invocation.subcommand_args.value_of("rev"); | |
37 | let file_args = match invocation.subcommand_args.values_of("files") { |
|
37 | let file_args = match invocation.subcommand_args.values_of("files") { | |
38 | Some(files) => files.collect(), |
|
38 | Some(files) => files.collect(), | |
39 | None => vec![], |
|
39 | None => vec![], | |
40 | }; |
|
40 | }; | |
41 |
|
41 | |||
42 | let repo = invocation.repo?; |
|
42 | let repo = invocation.repo?; | |
43 | let cwd = hg::utils::current_dir()?; |
|
43 | let cwd = hg::utils::current_dir()?; | |
44 | let working_directory = repo.working_directory_path(); |
|
44 | let working_directory = repo.working_directory_path(); | |
45 | let working_directory = cwd.join(working_directory); // Make it absolute |
|
45 | let working_directory = cwd.join(working_directory); // Make it absolute | |
46 |
|
46 | |||
47 | let mut files = vec![]; |
|
47 | let mut files = vec![]; | |
48 | for file in file_args.iter() { |
|
48 | for file in file_args.iter() { | |
49 | if file.starts_with("set:") { |
|
49 | if file.starts_with("set:") { | |
50 | let message = "fileset"; |
|
50 | let message = "fileset"; | |
51 | return Err(CommandError::unsupported(message)); |
|
51 | return Err(CommandError::unsupported(message)); | |
52 | } |
|
52 | } | |
53 |
|
53 | |||
54 | let normalized = cwd.join(&file); |
|
54 | let normalized = cwd.join(&file); | |
55 | // TODO: actually normalize `..` path segments etc? |
|
55 | // TODO: actually normalize `..` path segments etc? | |
56 | let dotted = normalized.components().any(|c| c.as_os_str() == ".."); |
|
56 | let dotted = normalized.components().any(|c| c.as_os_str() == ".."); | |
57 | if file == &"." || dotted { |
|
57 | if file == &"." || dotted { | |
58 | let message = "`..` or `.` path segment"; |
|
58 | let message = "`..` or `.` path segment"; | |
59 | return Err(CommandError::unsupported(message)); |
|
59 | return Err(CommandError::unsupported(message)); | |
60 | } |
|
60 | } | |
61 | let stripped = normalized |
|
61 | let stripped = normalized | |
62 | .strip_prefix(&working_directory) |
|
62 | .strip_prefix(&working_directory) | |
63 | // TODO: error message for path arguments outside of the repo |
|
63 | // TODO: error message for path arguments outside of the repo | |
64 | .map_err(|_| CommandError::abort(""))?; |
|
64 | .map_err(|_| CommandError::abort(""))?; | |
65 | let hg_file = HgPathBuf::try_from(stripped.to_path_buf()) |
|
65 | let hg_file = HgPathBuf::try_from(stripped.to_path_buf()) | |
66 | .map_err(|e| CommandError::abort(e.to_string()))?; |
|
66 | .map_err(|e| CommandError::abort(e.to_string()))?; | |
67 | files.push(hg_file); |
|
67 | files.push(hg_file); | |
68 | } |
|
68 | } | |
69 | // TODO probably move this to a util function like `repo.default_rev` or |
|
69 | // TODO probably move this to a util function like `repo.default_rev` or | |
70 | // something when it's used somewhere else |
|
70 | // something when it's used somewhere else | |
71 | let rev = match rev { |
|
71 | let rev = match rev { | |
72 | Some(r) => r.to_string(), |
|
72 | Some(r) => r.to_string(), | |
73 | None => format!("{:x}", repo.dirstate_parents()?.p1), |
|
73 | None => format!("{:x}", repo.dirstate_parents()?.p1), | |
74 | }; |
|
74 | }; | |
75 |
|
75 | |||
76 |
let output = cat(&repo, &rev, |
|
76 | let output = cat(&repo, &rev, files).map_err(|e| (e, rev.as_str()))?; | |
77 | invocation.ui.write_stdout(&output.concatenated)?; |
|
77 | invocation.ui.write_stdout(&output.concatenated)?; | |
78 | if !output.missing.is_empty() { |
|
78 | if !output.missing.is_empty() { | |
79 | let short = format!("{:x}", output.node.short()).into_bytes(); |
|
79 | let short = format!("{:x}", output.node.short()).into_bytes(); | |
80 | for path in &output.missing { |
|
80 | for path in &output.missing { | |
81 | invocation.ui.write_stderr(&format_bytes!( |
|
81 | invocation.ui.write_stderr(&format_bytes!( | |
82 | b"{}: no such file in rev {}\n", |
|
82 | b"{}: no such file in rev {}\n", | |
83 | path.as_bytes(), |
|
83 | path.as_bytes(), | |
84 | short |
|
84 | short | |
85 | ))?; |
|
85 | ))?; | |
86 | } |
|
86 | } | |
87 | } |
|
87 | } | |
88 | if output.found_any { |
|
88 | if output.found_any { | |
89 | Ok(()) |
|
89 | Ok(()) | |
90 | } else { |
|
90 | } else { | |
91 | Err(CommandError::Unsuccessful) |
|
91 | Err(CommandError::Unsuccessful) | |
92 | } |
|
92 | } | |
93 | } |
|
93 | } |
General Comments 0
You need to be logged in to leave comments.
Login now