##// 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 // 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: &'a [HgPathBuf],
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<_> = files
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, &files).map_err(|e| (e, rev.as_str()))?;
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