Show More
@@ -17,30 +17,49 b' use crate::revlog::Node;' | |||
|
17 | 17 | use crate::utils::files::get_path_from_bytes; |
|
18 | 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 | 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 | 36 | /// * `root`: Repository root |
|
25 | 37 | /// * `rev`: The revision to cat the files from. |
|
26 | 38 | /// * `files`: The files to output. |
|
27 | pub fn cat( | |
|
39 | pub fn cat<'a>( | |
|
28 | 40 | repo: &Repo, |
|
29 | 41 | revset: &str, |
|
30 | files: &[HgPathBuf], | |
|
31 |
) -> Result< |
|
|
42 | files: &'a [HgPathBuf], | |
|
43 | ) -> Result<CatOutput, RevlogError> { | |
|
32 | 44 | let rev = crate::revset::resolve_single(revset, repo)?; |
|
33 | 45 | let changelog = Changelog::open(repo)?; |
|
34 | 46 | let manifest = Manifest::open(repo)?; |
|
35 | 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 | 51 | let manifest_node = |
|
37 | 52 | Node::from_hex_for_repo(&changelog_entry.manifest_node()?)?; |
|
38 | 53 | let manifest_entry = manifest.get_node(manifest_node.into())?; |
|
39 | 54 | let mut bytes = vec![]; |
|
55 | let mut matched = vec![false; files.len()]; | |
|
56 | let mut found_any = false; | |
|
40 | 57 | |
|
41 | 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 | 60 | if cat_file.as_bytes() == manifest_file.as_bytes() { |
|
61 | *is_matched = true; | |
|
62 | found_any = true; | |
|
44 | 63 | let index_path = store_path(manifest_file, b".i"); |
|
45 | 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 | 101 | fn store_path(hg_path: &HgPath, suffix: &[u8]) -> PathBuf { |
@@ -6,7 +6,7 b' mod cat;' | |||
|
6 | 6 | mod debugdata; |
|
7 | 7 | mod dirstate_status; |
|
8 | 8 | mod list_tracked_files; |
|
9 | pub use cat::cat; | |
|
9 | pub use cat::{cat, CatOutput}; | |
|
10 | 10 | pub use debugdata::{debug_data, DebugDataKind}; |
|
11 | 11 | pub use list_tracked_files::Dirstate; |
|
12 | 12 | pub use list_tracked_files::{list_rev_tracked_files, FilesForRev}; |
@@ -1,8 +1,8 b'' | |||
|
1 | 1 | use crate::errors::HgError; |
|
2 | 2 | use crate::repo::Repo; |
|
3 | 3 | use crate::revlog::revlog::{Revlog, RevlogError}; |
|
4 | use crate::revlog::NodePrefix; | |
|
5 | 4 | use crate::revlog::Revision; |
|
5 | use crate::revlog::{Node, NodePrefix}; | |
|
6 | 6 | |
|
7 | 7 | /// A specialized `Revlog` to work with `changelog` data format. |
|
8 | 8 | pub struct Changelog { |
@@ -34,6 +34,10 b' impl Changelog {' | |||
|
34 | 34 | let bytes = self.revlog.get_rev_data(rev)?; |
|
35 | 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 | 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 | 31 | /// see also `NODES_BYTES_LENGTH` about it being private. |
|
32 | 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 | 37 | /// Private alias for readability and to ease future change |
|
35 | 38 | type NodeData = [u8; NODE_BYTES_LENGTH]; |
|
36 | 39 | |
@@ -164,6 +167,13 b' impl Node {' | |||
|
164 | 167 | pub fn as_bytes(&self) -> &[u8] { |
|
165 | 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 | 179 | /// The beginning of a binary revision SHA. |
@@ -49,7 +49,7 b' pub struct Revlog {' | |||
|
49 | 49 | /// When index and data are not interleaved: bytes of the revlog index. |
|
50 | 50 | /// When index and data are interleaved: bytes of the revlog index and |
|
51 | 51 | /// data. |
|
52 | index: Index, | |
|
52 | pub(crate) index: Index, | |
|
53 | 53 | /// When index and data are not interleaved: bytes of the revlog data |
|
54 | 54 | data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>>, |
|
55 | 55 | /// When present on disk: the persistent nodemap for this revlog |
@@ -1,5 +1,6 b'' | |||
|
1 | 1 | use crate::error::CommandError; |
|
2 | 2 | use clap::Arg; |
|
3 | use format_bytes::format_bytes; | |
|
3 | 4 | use hg::operations::cat; |
|
4 | 5 | use hg::utils::hg_path::HgPathBuf; |
|
5 | 6 | use micro_timer::timed; |
@@ -58,9 +59,23 b' pub fn run(invocation: &crate::CliInvoca' | |||
|
58 | 59 | |
|
59 | 60 | match rev { |
|
60 | 61 | Some(rev) => { |
|
61 |
let |
|
|
62 |
invocation.ui.write_stdout(& |
|
|
63 | Ok(()) | |
|
62 | let output = cat(&repo, rev, &files).map_err(|e| (e, rev))?; | |
|
63 | invocation.ui.write_stdout(&output.concatenated)?; | |
|
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 | 80 | None => Err(CommandError::unsupported( |
|
66 | 81 | "`rhg cat` without `--rev` / `-r`", |
@@ -15,6 +15,9 b' pub enum CommandError {' | |||
|
15 | 15 | /// Exit with an error message and "standard" failure exit code. |
|
16 | 16 | Abort { message: Vec<u8> }, |
|
17 | 17 | |
|
18 | /// Exit with a failure exit code but no message. | |
|
19 | Unsuccessful, | |
|
20 | ||
|
18 | 21 | /// Encountered something (such as a CLI argument, repository layout, …) |
|
19 | 22 | /// not supported by this version of `rhg`. Depending on configuration |
|
20 | 23 | /// `rhg` may attempt to silently fall back to Python-based `hg`, which |
@@ -6,5 +6,8 b' pub const OK: ExitCode = 0;' | |||
|
6 | 6 | /// Generic abort |
|
7 | 7 | pub const ABORT: ExitCode = 255; |
|
8 | 8 | |
|
9 | /// Generic something completed but did not succeed | |
|
10 | pub const UNSUCCESSFUL: ExitCode = 1; | |
|
11 | ||
|
9 | 12 | /// Command or feature not implemented by rhg |
|
10 | 13 | pub const UNIMPLEMENTED: ExitCode = 252; |
@@ -186,6 +186,7 b' fn exit_code(result: &Result<(), Command' | |||
|
186 | 186 | match result { |
|
187 | 187 | Ok(()) => exitcode::OK, |
|
188 | 188 | Err(CommandError::Abort { .. }) => exitcode::ABORT, |
|
189 | Err(CommandError::Unsuccessful) => exitcode::UNSUCCESSFUL, | |
|
189 | 190 | |
|
190 | 191 | // Exit with a specific code and no error message to let a potential |
|
191 | 192 | // wrapper script fallback to Python-based Mercurial. |
@@ -242,6 +243,7 b' fn exit(' | |||
|
242 | 243 | } |
|
243 | 244 | match &result { |
|
244 | 245 | Ok(_) => {} |
|
246 | Err(CommandError::Unsuccessful) => {} | |
|
245 | 247 | Err(CommandError::Abort { message }) => { |
|
246 | 248 | if !message.is_empty() { |
|
247 | 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