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