##// END OF EJS Templates
rhg: internally, return a structured representation from hg cat...
Arseniy Alekseyev -
r49051:027ebad9 default
parent child Browse files
Show More
@@ -1,115 +1,115 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::HgPath;
12 use crate::utils::hg_path::HgPath;
13 use crate::utils::hg_path::HgPathBuf;
14
13
15 use itertools::put_back;
14 use itertools::put_back;
16 use itertools::PutBack;
15 use itertools::PutBack;
17 use std::cmp::Ordering;
16 use std::cmp::Ordering;
18
17
19 pub struct CatOutput {
18 pub struct CatOutput<'a> {
20 /// Whether any file in the manifest matched the paths given as CLI
19 /// Whether any file in the manifest matched the paths given as CLI
21 /// arguments
20 /// arguments
22 pub found_any: bool,
21 pub found_any: bool,
23 /// The contents of matching files, in manifest order
22 /// The contents of matching files, in manifest order
24 pub concatenated: Vec<u8>,
23 pub results: Vec<(&'a HgPath, Vec<u8>)>,
25 /// Which of the CLI arguments did not match any manifest file
24 /// Which of the CLI arguments did not match any manifest file
26 pub missing: Vec<HgPathBuf>,
25 pub missing: Vec<&'a HgPath>,
27 /// The node ID that the given revset was resolved to
26 /// The node ID that the given revset was resolved to
28 pub node: Node,
27 pub node: Node,
29 }
28 }
30
29
31 // Find an item in an iterator over a sorted collection.
30 // Find an item in an iterator over a sorted collection.
32 fn find_item<'a, 'b, 'c, D, I: Iterator<Item = (&'a HgPath, D)>>(
31 fn find_item<'a, 'b, 'c, D, I: Iterator<Item = (&'a HgPath, D)>>(
33 i: &mut PutBack<I>,
32 i: &mut PutBack<I>,
34 needle: &'b HgPath,
33 needle: &'b HgPath,
35 ) -> Option<I::Item> {
34 ) -> Option<D> {
36 loop {
35 loop {
37 match i.next() {
36 match i.next() {
38 None => return None,
37 None => return None,
39 Some(val) => match needle.as_bytes().cmp(val.0.as_bytes()) {
38 Some(val) => match needle.as_bytes().cmp(val.0.as_bytes()) {
40 Ordering::Less => {
39 Ordering::Less => {
41 i.put_back(val);
40 i.put_back(val);
42 return None;
41 return None;
43 }
42 }
44 Ordering::Greater => continue,
43 Ordering::Greater => continue,
45 Ordering::Equal => return Some(val),
44 Ordering::Equal => return Some(val.1),
46 },
45 },
47 }
46 }
48 }
47 }
49 }
48 }
50
49
51 fn find_files_in_manifest<
50 fn find_files_in_manifest<
52 'a,
51 'manifest,
53 'b,
52 'query,
54 D,
53 Data,
55 I: Iterator<Item = (&'a HgPath, D)>,
54 Manifest: Iterator<Item = (&'manifest HgPath, Data)>,
56 J: Iterator<Item = &'b HgPath>,
55 Query: Iterator<Item = &'query HgPath>,
57 >(
56 >(
58 manifest: I,
57 manifest: Manifest,
59 files: J,
58 query: Query,
60 ) -> (Vec<(&'a HgPath, D)>, Vec<&'b HgPath>) {
59 ) -> (Vec<(&'query HgPath, Data)>, Vec<&'query HgPath>) {
61 let mut manifest = put_back(manifest);
60 let mut manifest = put_back(manifest);
62 let mut res = vec![];
61 let mut res = vec![];
63 let mut missing = vec![];
62 let mut missing = vec![];
64
63
65 for file in files {
64 for file in query {
66 match find_item(&mut manifest, file) {
65 match find_item(&mut manifest, file) {
67 None => missing.push(file),
66 None => missing.push(file),
68 Some(item) => res.push(item),
67 Some(item) => res.push((file, item)),
69 }
68 }
70 }
69 }
71 return (res, missing);
70 return (res, missing);
72 }
71 }
73
72
74 /// Output the given revision of files
73 /// Output the given revision of files
75 ///
74 ///
76 /// * `root`: Repository root
75 /// * `root`: Repository root
77 /// * `rev`: The revision to cat the files from.
76 /// * `rev`: The revision to cat the files from.
78 /// * `files`: The files to output.
77 /// * `files`: The files to output.
79 pub fn cat<'a>(
78 pub fn cat<'a>(
80 repo: &Repo,
79 repo: &Repo,
81 revset: &str,
80 revset: &str,
82 mut files: Vec<HgPathBuf>,
81 mut files: Vec<&'a HgPath>,
83 ) -> Result<CatOutput, RevlogError> {
82 ) -> Result<CatOutput<'a>, RevlogError> {
84 let rev = crate::revset::resolve_single(revset, repo)?;
83 let rev = crate::revset::resolve_single(revset, repo)?;
85 let manifest = repo.manifest_for_rev(rev)?;
84 let manifest = repo.manifest_for_rev(rev)?;
86 let node = *repo
85 let node = *repo
87 .changelog()?
86 .changelog()?
88 .node_from_rev(rev)
87 .node_from_rev(rev)
89 .expect("should succeed when repo.manifest did");
88 .expect("should succeed when repo.manifest did");
90 let mut bytes: Vec<u8> = vec![];
89 let mut results: Vec<(&'a HgPath, Vec<u8>)> = vec![];
91 let mut found_any = false;
90 let mut found_any = false;
92
91
93 files.sort_unstable();
92 files.sort_unstable();
94
93
95 let (found, missing) = find_files_in_manifest(
94 let (found, missing) = find_files_in_manifest(
96 manifest.files_with_nodes(),
95 manifest.files_with_nodes(),
97 files.iter().map(|f| f.as_ref()),
96 files.into_iter().map(|f| f.as_ref()),
98 );
97 );
99
98
100 for (manifest_file, node_bytes) in found {
99 for (file_path, node_bytes) in found {
101 found_any = true;
100 found_any = true;
102 let file_log = repo.filelog(manifest_file)?;
101 let file_log = repo.filelog(file_path)?;
103 let file_node = Node::from_hex_for_repo(node_bytes)?;
102 let file_node = Node::from_hex_for_repo(node_bytes)?;
104 bytes.extend(file_log.data_for_node(file_node)?.data()?);
103 results.push((
104 file_path,
105 file_log.data_for_node(file_node)?.into_data()?,
106 ));
105 }
107 }
106
108
107 let missing: Vec<HgPathBuf> =
108 missing.iter().map(|file| (*file).to_owned()).collect();
109 Ok(CatOutput {
109 Ok(CatOutput {
110 found_any,
110 found_any,
111 concatenated: bytes,
111 results,
112 missing,
112 missing,
113 node,
113 node,
114 })
114 })
115 }
115 }
@@ -1,79 +1,98 b''
1 use crate::errors::HgError;
1 use crate::errors::HgError;
2 use crate::repo::Repo;
2 use crate::repo::Repo;
3 use crate::revlog::path_encode::path_encode;
3 use crate::revlog::path_encode::path_encode;
4 use crate::revlog::revlog::{Revlog, RevlogError};
4 use crate::revlog::revlog::{Revlog, RevlogError};
5 use crate::revlog::NodePrefix;
5 use crate::revlog::NodePrefix;
6 use crate::revlog::Revision;
6 use crate::revlog::Revision;
7 use crate::utils::files::get_path_from_bytes;
7 use crate::utils::files::get_path_from_bytes;
8 use crate::utils::hg_path::HgPath;
8 use crate::utils::hg_path::HgPath;
9 use crate::utils::SliceExt;
9 use crate::utils::SliceExt;
10 use std::borrow::Cow;
11 use std::path::PathBuf;
10 use std::path::PathBuf;
12
11
13 /// A specialized `Revlog` to work with file data logs.
12 /// A specialized `Revlog` to work with file data logs.
14 pub struct Filelog {
13 pub struct Filelog {
15 /// The generic `revlog` format.
14 /// The generic `revlog` format.
16 revlog: Revlog,
15 revlog: Revlog,
17 }
16 }
18
17
19 impl Filelog {
18 impl Filelog {
20 pub fn open(repo: &Repo, file_path: &HgPath) -> Result<Self, HgError> {
19 pub fn open(repo: &Repo, file_path: &HgPath) -> Result<Self, HgError> {
21 let index_path = store_path(file_path, b".i");
20 let index_path = store_path(file_path, b".i");
22 let data_path = store_path(file_path, b".d");
21 let data_path = store_path(file_path, b".d");
23 let revlog = Revlog::open(repo, index_path, Some(&data_path))?;
22 let revlog = Revlog::open(repo, index_path, Some(&data_path))?;
24 Ok(Self { revlog })
23 Ok(Self { revlog })
25 }
24 }
26
25
27 /// The given node ID is that of the file as found in a manifest, not of a
26 /// The given node ID is that of the file as found in a manifest, not of a
28 /// changeset.
27 /// changeset.
29 pub fn data_for_node(
28 pub fn data_for_node(
30 &self,
29 &self,
31 file_node: impl Into<NodePrefix>,
30 file_node: impl Into<NodePrefix>,
32 ) -> Result<FilelogEntry, RevlogError> {
31 ) -> Result<FilelogEntry, RevlogError> {
33 let file_rev = self.revlog.rev_from_node(file_node.into())?;
32 let file_rev = self.revlog.rev_from_node(file_node.into())?;
34 self.data_for_rev(file_rev)
33 self.data_for_rev(file_rev)
35 }
34 }
36
35
37 /// The given revision is that of the file as found in a manifest, not of a
36 /// The given revision is that of the file as found in a manifest, not of a
38 /// changeset.
37 /// changeset.
39 pub fn data_for_rev(
38 pub fn data_for_rev(
40 &self,
39 &self,
41 file_rev: Revision,
40 file_rev: Revision,
42 ) -> Result<FilelogEntry, RevlogError> {
41 ) -> Result<FilelogEntry, RevlogError> {
43 let data = self.revlog.get_rev_data(file_rev)?;
42 let data: Vec<u8> = self.revlog.get_rev_data(file_rev)?;
44 Ok(FilelogEntry(data.into()))
43 Ok(FilelogEntry(data.into()))
45 }
44 }
46 }
45 }
47
46
48 fn store_path(hg_path: &HgPath, suffix: &[u8]) -> PathBuf {
47 fn store_path(hg_path: &HgPath, suffix: &[u8]) -> PathBuf {
49 let encoded_bytes =
48 let encoded_bytes =
50 path_encode(&[b"data/", hg_path.as_bytes(), suffix].concat());
49 path_encode(&[b"data/", hg_path.as_bytes(), suffix].concat());
51 get_path_from_bytes(&encoded_bytes).into()
50 get_path_from_bytes(&encoded_bytes).into()
52 }
51 }
53
52
54 pub struct FilelogEntry<'filelog>(Cow<'filelog, [u8]>);
53 pub struct FilelogEntry(Vec<u8>);
55
54
56 impl<'filelog> FilelogEntry<'filelog> {
55 impl FilelogEntry {
57 /// Split into metadata and data
56 /// Split into metadata and data
58 pub fn split(&self) -> Result<(Option<&[u8]>, &[u8]), HgError> {
57 /// Returns None if there is no metadata, so the entire entry is data.
58 fn split_metadata(&self) -> Result<Option<(&[u8], &[u8])>, HgError> {
59 const DELIMITER: &[u8; 2] = &[b'\x01', b'\n'];
59 const DELIMITER: &[u8; 2] = &[b'\x01', b'\n'];
60
60
61 if let Some(rest) = self.0.drop_prefix(DELIMITER) {
61 if let Some(rest) = self.0.drop_prefix(DELIMITER) {
62 if let Some((metadata, data)) = rest.split_2_by_slice(DELIMITER) {
62 if let Some((metadata, data)) = rest.split_2_by_slice(DELIMITER) {
63 Ok((Some(metadata), data))
63 Ok(Some((metadata, data)))
64 } else {
64 } else {
65 Err(HgError::corrupted(
65 Err(HgError::corrupted(
66 "Missing metadata end delimiter in filelog entry",
66 "Missing metadata end delimiter in filelog entry",
67 ))
67 ))
68 }
68 }
69 } else {
69 } else {
70 Ok(None)
71 }
72 }
73
74 /// Split into metadata and data
75 pub fn split(&self) -> Result<(Option<&[u8]>, &[u8]), HgError> {
76 if let Some((metadata, data)) = self.split_metadata()? {
77 Ok((Some(metadata), data))
78 } else {
70 Ok((None, &self.0))
79 Ok((None, &self.0))
71 }
80 }
72 }
81 }
73
82
74 /// Returns the file contents at this revision, stripped of any metadata
83 /// Returns the file contents at this revision, stripped of any metadata
75 pub fn data(&self) -> Result<&[u8], HgError> {
84 pub fn data(&self) -> Result<&[u8], HgError> {
76 let (_metadata, data) = self.split()?;
85 let (_metadata, data) = self.split()?;
77 Ok(data)
86 Ok(data)
78 }
87 }
88
89 /// Consume the entry, and convert it into data, discarding any metadata,
90 /// if present.
91 pub fn into_data(self) -> Result<Vec<u8>, HgError> {
92 if let Some((_metadata, data)) = self.split_metadata()? {
93 Ok(data.to_owned())
94 } else {
95 Ok(self.0)
96 }
97 }
79 }
98 }
@@ -1,93 +1,96 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 let files = files.iter().map(|file| file.as_ref()).collect();
69 // TODO probably move this to a util function like `repo.default_rev` or
70 // TODO probably move this to a util function like `repo.default_rev` or
70 // something when it's used somewhere else
71 // something when it's used somewhere else
71 let rev = match rev {
72 let rev = match rev {
72 Some(r) => r.to_string(),
73 Some(r) => r.to_string(),
73 None => format!("{:x}", repo.dirstate_parents()?.p1),
74 None => format!("{:x}", repo.dirstate_parents()?.p1),
74 };
75 };
75
76
76 let output = cat(&repo, &rev, files).map_err(|e| (e, rev.as_str()))?;
77 let output = cat(&repo, &rev, files).map_err(|e| (e, rev.as_str()))?;
77 invocation.ui.write_stdout(&output.concatenated)?;
78 for (_file, contents) in output.results {
79 invocation.ui.write_stdout(&contents)?;
80 }
78 if !output.missing.is_empty() {
81 if !output.missing.is_empty() {
79 let short = format!("{:x}", output.node.short()).into_bytes();
82 let short = format!("{:x}", output.node.short()).into_bytes();
80 for path in &output.missing {
83 for path in &output.missing {
81 invocation.ui.write_stderr(&format_bytes!(
84 invocation.ui.write_stderr(&format_bytes!(
82 b"{}: no such file in rev {}\n",
85 b"{}: no such file in rev {}\n",
83 path.as_bytes(),
86 path.as_bytes(),
84 short
87 short
85 ))?;
88 ))?;
86 }
89 }
87 }
90 }
88 if output.found_any {
91 if output.found_any {
89 Ok(())
92 Ok(())
90 } else {
93 } else {
91 Err(CommandError::Unsuccessful)
94 Err(CommandError::Unsuccessful)
92 }
95 }
93 }
96 }
General Comments 0
You need to be logged in to leave comments. Login now