Show More
@@ -17,30 +17,49 b' use crate::revlog::Node;' | |||||
17 | use crate::utils::files::get_path_from_bytes; |
|
17 | use crate::utils::files::get_path_from_bytes; | |
18 | use crate::utils::hg_path::{HgPath, HgPathBuf}; |
|
18 | use crate::utils::hg_path::{HgPath, HgPathBuf}; | |
19 |
|
19 | |||
|
20 | pub struct CatOutput { | |||
|
21 | /// Whether any file in the manifest matched the paths given as CLI | |||
|
22 | /// arguments | |||
|
23 | pub found_any: bool, | |||
|
24 | /// The contents of matching files, in manifest order | |||
|
25 | pub concatenated: Vec<u8>, | |||
|
26 | /// Which of the CLI arguments did not match any manifest file | |||
|
27 | pub missing: Vec<HgPathBuf>, | |||
|
28 | /// The node ID that the given revset was resolved to | |||
|
29 | pub node: Node, | |||
|
30 | } | |||
|
31 | ||||
20 | const METADATA_DELIMITER: [u8; 2] = [b'\x01', b'\n']; |
|
32 | const METADATA_DELIMITER: [u8; 2] = [b'\x01', b'\n']; | |
21 |
|
33 | |||
22 | /// List files under Mercurial control at a given revision. |
|
34 | /// Output the given revision of files | |
23 | /// |
|
35 | /// | |
24 | /// * `root`: Repository root |
|
36 | /// * `root`: Repository root | |
25 | /// * `rev`: The revision to cat the files from. |
|
37 | /// * `rev`: The revision to cat the files from. | |
26 | /// * `files`: The files to output. |
|
38 | /// * `files`: The files to output. | |
27 | pub fn cat( |
|
39 | pub fn cat<'a>( | |
28 | repo: &Repo, |
|
40 | repo: &Repo, | |
29 | revset: &str, |
|
41 | revset: &str, | |
30 | files: &[HgPathBuf], |
|
42 | files: &'a [HgPathBuf], | |
31 |
) -> Result< |
|
43 | ) -> Result<CatOutput, RevlogError> { | |
32 | let rev = crate::revset::resolve_single(revset, repo)?; |
|
44 | let rev = crate::revset::resolve_single(revset, repo)?; | |
33 | let changelog = Changelog::open(repo)?; |
|
45 | let changelog = Changelog::open(repo)?; | |
34 | let manifest = Manifest::open(repo)?; |
|
46 | let manifest = Manifest::open(repo)?; | |
35 | let changelog_entry = changelog.get_rev(rev)?; |
|
47 | let changelog_entry = changelog.get_rev(rev)?; | |
|
48 | let node = *changelog | |||
|
49 | .node_from_rev(rev) | |||
|
50 | .expect("should succeed when changelog.get_rev did"); | |||
36 | let manifest_node = |
|
51 | let manifest_node = | |
37 | Node::from_hex_for_repo(&changelog_entry.manifest_node()?)?; |
|
52 | Node::from_hex_for_repo(&changelog_entry.manifest_node()?)?; | |
38 | let manifest_entry = manifest.get_node(manifest_node.into())?; |
|
53 | let manifest_entry = manifest.get_node(manifest_node.into())?; | |
39 | let mut bytes = vec![]; |
|
54 | let mut bytes = vec![]; | |
|
55 | let mut matched = vec![false; files.len()]; | |||
|
56 | let mut found_any = false; | |||
40 |
|
57 | |||
41 | for (manifest_file, node_bytes) in manifest_entry.files_with_nodes() { |
|
58 | for (manifest_file, node_bytes) in manifest_entry.files_with_nodes() { | |
42 | for cat_file in files.iter() { |
|
59 | for (cat_file, is_matched) in files.iter().zip(&mut matched) { | |
43 | if cat_file.as_bytes() == manifest_file.as_bytes() { |
|
60 | if cat_file.as_bytes() == manifest_file.as_bytes() { | |
|
61 | *is_matched = true; | |||
|
62 | found_any = true; | |||
44 | let index_path = store_path(manifest_file, b".i"); |
|
63 | let index_path = store_path(manifest_file, b".i"); | |
45 | let data_path = store_path(manifest_file, b".d"); |
|
64 | let data_path = store_path(manifest_file, b".d"); | |
46 |
|
65 | |||
@@ -65,7 +84,18 b' pub fn cat(' | |||||
65 | } |
|
84 | } | |
66 | } |
|
85 | } | |
67 |
|
86 | |||
68 | Ok(bytes) |
|
87 | let missing: Vec<_> = files | |
|
88 | .iter() | |||
|
89 | .zip(&matched) | |||
|
90 | .filter(|pair| !*pair.1) | |||
|
91 | .map(|pair| pair.0.clone()) | |||
|
92 | .collect(); | |||
|
93 | Ok(CatOutput { | |||
|
94 | found_any, | |||
|
95 | concatenated: bytes, | |||
|
96 | missing, | |||
|
97 | node, | |||
|
98 | }) | |||
69 | } |
|
99 | } | |
70 |
|
100 | |||
71 | fn store_path(hg_path: &HgPath, suffix: &[u8]) -> PathBuf { |
|
101 | fn store_path(hg_path: &HgPath, suffix: &[u8]) -> PathBuf { |
@@ -6,7 +6,7 b' mod cat;' | |||||
6 | mod debugdata; |
|
6 | mod debugdata; | |
7 | mod dirstate_status; |
|
7 | mod dirstate_status; | |
8 | mod list_tracked_files; |
|
8 | mod list_tracked_files; | |
9 | pub use cat::cat; |
|
9 | pub use cat::{cat, CatOutput}; | |
10 | pub use debugdata::{debug_data, DebugDataKind}; |
|
10 | pub use debugdata::{debug_data, DebugDataKind}; | |
11 | pub use list_tracked_files::Dirstate; |
|
11 | pub use list_tracked_files::Dirstate; | |
12 | pub use list_tracked_files::{list_rev_tracked_files, FilesForRev}; |
|
12 | pub use list_tracked_files::{list_rev_tracked_files, FilesForRev}; |
@@ -1,8 +1,8 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::revlog::{Revlog, RevlogError}; |
|
3 | use crate::revlog::revlog::{Revlog, RevlogError}; | |
4 | use crate::revlog::NodePrefix; |
|
|||
5 | use crate::revlog::Revision; |
|
4 | use crate::revlog::Revision; | |
|
5 | use crate::revlog::{Node, NodePrefix}; | |||
6 |
|
6 | |||
7 | /// A specialized `Revlog` to work with `changelog` data format. |
|
7 | /// A specialized `Revlog` to work with `changelog` data format. | |
8 | pub struct Changelog { |
|
8 | pub struct Changelog { | |
@@ -34,6 +34,10 b' impl Changelog {' | |||||
34 | let bytes = self.revlog.get_rev_data(rev)?; |
|
34 | let bytes = self.revlog.get_rev_data(rev)?; | |
35 | Ok(ChangelogEntry { bytes }) |
|
35 | Ok(ChangelogEntry { bytes }) | |
36 | } |
|
36 | } | |
|
37 | ||||
|
38 | pub fn node_from_rev(&self, rev: Revision) -> Option<&Node> { | |||
|
39 | Some(self.revlog.index.get_entry(rev)?.hash()) | |||
|
40 | } | |||
37 | } |
|
41 | } | |
38 |
|
42 | |||
39 | /// `Changelog` entry which knows how to interpret the `changelog` data bytes. |
|
43 | /// `Changelog` entry which knows how to interpret the `changelog` data bytes. |
@@ -31,6 +31,9 b' pub const NULL_NODE_ID: [u8; NODE_BYTES_' | |||||
31 | /// see also `NODES_BYTES_LENGTH` about it being private. |
|
31 | /// see also `NODES_BYTES_LENGTH` about it being private. | |
32 | const NODE_NYBBLES_LENGTH: usize = 2 * NODE_BYTES_LENGTH; |
|
32 | const NODE_NYBBLES_LENGTH: usize = 2 * NODE_BYTES_LENGTH; | |
33 |
|
33 | |||
|
34 | /// Default for UI presentation | |||
|
35 | const SHORT_PREFIX_DEFAULT_NYBBLES_LENGTH: u8 = 12; | |||
|
36 | ||||
34 | /// Private alias for readability and to ease future change |
|
37 | /// Private alias for readability and to ease future change | |
35 | type NodeData = [u8; NODE_BYTES_LENGTH]; |
|
38 | type NodeData = [u8; NODE_BYTES_LENGTH]; | |
36 |
|
39 | |||
@@ -164,6 +167,13 b' impl Node {' | |||||
164 | pub fn as_bytes(&self) -> &[u8] { |
|
167 | pub fn as_bytes(&self) -> &[u8] { | |
165 | &self.data |
|
168 | &self.data | |
166 | } |
|
169 | } | |
|
170 | ||||
|
171 | pub fn short(&self) -> NodePrefix { | |||
|
172 | NodePrefix { | |||
|
173 | nybbles_len: SHORT_PREFIX_DEFAULT_NYBBLES_LENGTH, | |||
|
174 | data: self.data, | |||
|
175 | } | |||
|
176 | } | |||
167 | } |
|
177 | } | |
168 |
|
178 | |||
169 | /// The beginning of a binary revision SHA. |
|
179 | /// The beginning of a binary revision SHA. |
@@ -49,7 +49,7 b' pub struct Revlog {' | |||||
49 | /// When index and data are not interleaved: bytes of the revlog index. |
|
49 | /// When index and data are not interleaved: bytes of the revlog index. | |
50 | /// When index and data are interleaved: bytes of the revlog index and |
|
50 | /// When index and data are interleaved: bytes of the revlog index and | |
51 | /// data. |
|
51 | /// data. | |
52 | index: Index, |
|
52 | pub(crate) index: Index, | |
53 | /// When index and data are not interleaved: bytes of the revlog data |
|
53 | /// When index and data are not interleaved: bytes of the revlog data | |
54 | data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>>, |
|
54 | data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>>, | |
55 | /// When present on disk: the persistent nodemap for this revlog |
|
55 | /// When present on disk: the persistent nodemap for this revlog |
@@ -1,5 +1,6 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 hg::operations::cat; |
|
4 | use hg::operations::cat; | |
4 | use hg::utils::hg_path::HgPathBuf; |
|
5 | use hg::utils::hg_path::HgPathBuf; | |
5 | use micro_timer::timed; |
|
6 | use micro_timer::timed; | |
@@ -58,9 +59,23 b' pub fn run(invocation: &crate::CliInvoca' | |||||
58 |
|
59 | |||
59 | match rev { |
|
60 | match rev { | |
60 | Some(rev) => { |
|
61 | Some(rev) => { | |
61 |
let |
|
62 | let output = cat(&repo, rev, &files).map_err(|e| (e, rev))?; | |
62 |
invocation.ui.write_stdout(& |
|
63 | invocation.ui.write_stdout(&output.concatenated)?; | |
63 | Ok(()) |
|
64 | if !output.missing.is_empty() { | |
|
65 | let short = format!("{:x}", output.node.short()).into_bytes(); | |||
|
66 | for path in &output.missing { | |||
|
67 | invocation.ui.write_stderr(&format_bytes!( | |||
|
68 | b"{}: no such file in rev {}\n", | |||
|
69 | path.as_bytes(), | |||
|
70 | short | |||
|
71 | ))?; | |||
|
72 | } | |||
|
73 | } | |||
|
74 | if output.found_any { | |||
|
75 | Ok(()) | |||
|
76 | } else { | |||
|
77 | Err(CommandError::Unsuccessful) | |||
|
78 | } | |||
64 | } |
|
79 | } | |
65 | None => Err(CommandError::unsupported( |
|
80 | None => Err(CommandError::unsupported( | |
66 | "`rhg cat` without `--rev` / `-r`", |
|
81 | "`rhg cat` without `--rev` / `-r`", |
@@ -15,6 +15,9 b' pub enum CommandError {' | |||||
15 | /// Exit with an error message and "standard" failure exit code. |
|
15 | /// Exit with an error message and "standard" failure exit code. | |
16 | Abort { message: Vec<u8> }, |
|
16 | Abort { message: Vec<u8> }, | |
17 |
|
17 | |||
|
18 | /// Exit with a failure exit code but no message. | |||
|
19 | Unsuccessful, | |||
|
20 | ||||
18 | /// Encountered something (such as a CLI argument, repository layout, …) |
|
21 | /// Encountered something (such as a CLI argument, repository layout, …) | |
19 | /// not supported by this version of `rhg`. Depending on configuration |
|
22 | /// not supported by this version of `rhg`. Depending on configuration | |
20 | /// `rhg` may attempt to silently fall back to Python-based `hg`, which |
|
23 | /// `rhg` may attempt to silently fall back to Python-based `hg`, which |
@@ -6,5 +6,8 b' pub const OK: ExitCode = 0;' | |||||
6 | /// Generic abort |
|
6 | /// Generic abort | |
7 | pub const ABORT: ExitCode = 255; |
|
7 | pub const ABORT: ExitCode = 255; | |
8 |
|
8 | |||
|
9 | /// Generic something completed but did not succeed | |||
|
10 | pub const UNSUCCESSFUL: ExitCode = 1; | |||
|
11 | ||||
9 | /// Command or feature not implemented by rhg |
|
12 | /// Command or feature not implemented by rhg | |
10 | pub const UNIMPLEMENTED: ExitCode = 252; |
|
13 | pub const UNIMPLEMENTED: ExitCode = 252; |
@@ -186,6 +186,7 b' fn exit_code(result: &Result<(), Command' | |||||
186 | match result { |
|
186 | match result { | |
187 | Ok(()) => exitcode::OK, |
|
187 | Ok(()) => exitcode::OK, | |
188 | Err(CommandError::Abort { .. }) => exitcode::ABORT, |
|
188 | Err(CommandError::Abort { .. }) => exitcode::ABORT, | |
|
189 | Err(CommandError::Unsuccessful) => exitcode::UNSUCCESSFUL, | |||
189 |
|
190 | |||
190 | // Exit with a specific code and no error message to let a potential |
|
191 | // Exit with a specific code and no error message to let a potential | |
191 | // wrapper script fallback to Python-based Mercurial. |
|
192 | // wrapper script fallback to Python-based Mercurial. | |
@@ -242,6 +243,7 b' fn exit(' | |||||
242 | } |
|
243 | } | |
243 | match &result { |
|
244 | match &result { | |
244 | Ok(_) => {} |
|
245 | Ok(_) => {} | |
|
246 | Err(CommandError::Unsuccessful) => {} | |||
245 | Err(CommandError::Abort { message }) => { |
|
247 | Err(CommandError::Abort { message }) => { | |
246 | if !message.is_empty() { |
|
248 | if !message.is_empty() { | |
247 | // Ignore errors when writing to stderr, we’re already exiting |
|
249 | // Ignore errors when writing to stderr, we’re already exiting |
General Comments 0
You need to be logged in to leave comments.
Login now