##// END OF EJS Templates
rhg: faster hg cat when many files are requested...
Arseniy Alekseyev -
r49038:6b5773f8 default
parent child Browse files
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: &'a [HgPathBuf],
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<_> = files
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, &files).map_err(|e| (e, rev.as_str()))?;
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