##// END OF EJS Templates
rhg: `cat` command: print error messages for missing files...
Simon Sapin -
r47478:b1f2c2b3 default
parent child Browse files
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<Vec<u8>, RevlogError> {
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 data = cat(&repo, rev, &files).map_err(|e| (e, rev))?;
62 invocation.ui.write_stdout(&data)?;
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 {
63 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