##// END OF EJS Templates
rhg: `cat` command: print error messages for missing files...
Simon Sapin -
r47478:b1f2c2b3 default
parent child Browse files
Show More
@@ -1,75 +1,105 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 std::path::PathBuf;
8 use std::path::PathBuf;
9
9
10 use crate::repo::Repo;
10 use crate::repo::Repo;
11 use crate::revlog::changelog::Changelog;
11 use crate::revlog::changelog::Changelog;
12 use crate::revlog::manifest::Manifest;
12 use crate::revlog::manifest::Manifest;
13 use crate::revlog::path_encode::path_encode;
13 use crate::revlog::path_encode::path_encode;
14 use crate::revlog::revlog::Revlog;
14 use crate::revlog::revlog::Revlog;
15 use crate::revlog::revlog::RevlogError;
15 use crate::revlog::revlog::RevlogError;
16 use crate::revlog::Node;
16 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<Vec<u8>, RevlogError> {
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
47 let file_log =
66 let file_log =
48 Revlog::open(repo, &index_path, Some(&data_path))?;
67 Revlog::open(repo, &index_path, Some(&data_path))?;
49 let file_node = Node::from_hex_for_repo(node_bytes)?;
68 let file_node = Node::from_hex_for_repo(node_bytes)?;
50 let file_rev = file_log.get_node_rev(file_node.into())?;
69 let file_rev = file_log.get_node_rev(file_node.into())?;
51 let data = file_log.get_rev_data(file_rev)?;
70 let data = file_log.get_rev_data(file_rev)?;
52 if data.starts_with(&METADATA_DELIMITER) {
71 if data.starts_with(&METADATA_DELIMITER) {
53 let end_delimiter_position = data
72 let end_delimiter_position = data
54 [METADATA_DELIMITER.len()..]
73 [METADATA_DELIMITER.len()..]
55 .windows(METADATA_DELIMITER.len())
74 .windows(METADATA_DELIMITER.len())
56 .position(|bytes| bytes == METADATA_DELIMITER);
75 .position(|bytes| bytes == METADATA_DELIMITER);
57 if let Some(position) = end_delimiter_position {
76 if let Some(position) = end_delimiter_position {
58 let offset = METADATA_DELIMITER.len() * 2;
77 let offset = METADATA_DELIMITER.len() * 2;
59 bytes.extend(data[position + offset..].iter());
78 bytes.extend(data[position + offset..].iter());
60 }
79 }
61 } else {
80 } else {
62 bytes.extend(data);
81 bytes.extend(data);
63 }
82 }
64 }
83 }
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 {
72 let encoded_bytes =
102 let encoded_bytes =
73 path_encode(&[b"data/", hg_path.as_bytes(), suffix].concat());
103 path_encode(&[b"data/", hg_path.as_bytes(), suffix].concat());
74 get_path_from_bytes(&encoded_bytes).into()
104 get_path_from_bytes(&encoded_bytes).into()
75 }
105 }
@@ -1,12 +1,12 b''
1 //! A distinction is made between operations and commands.
1 //! A distinction is made between operations and commands.
2 //! An operation is what can be done whereas a command is what is exposed by
2 //! An operation is what can be done whereas a command is what is exposed by
3 //! the cli. A single command can use several operations to achieve its goal.
3 //! the cli. A single command can use several operations to achieve its goal.
4
4
5 mod cat;
5 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,61 +1,65 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 {
9 /// The generic `revlog` format.
9 /// The generic `revlog` format.
10 pub(crate) revlog: Revlog,
10 pub(crate) revlog: Revlog,
11 }
11 }
12
12
13 impl Changelog {
13 impl Changelog {
14 /// Open the `changelog` of a repository given by its root.
14 /// Open the `changelog` of a repository given by its root.
15 pub fn open(repo: &Repo) -> Result<Self, RevlogError> {
15 pub fn open(repo: &Repo) -> Result<Self, RevlogError> {
16 let revlog = Revlog::open(repo, "00changelog.i", None)?;
16 let revlog = Revlog::open(repo, "00changelog.i", None)?;
17 Ok(Self { revlog })
17 Ok(Self { revlog })
18 }
18 }
19
19
20 /// Return the `ChangelogEntry` a given node id.
20 /// Return the `ChangelogEntry` a given node id.
21 pub fn get_node(
21 pub fn get_node(
22 &self,
22 &self,
23 node: NodePrefix,
23 node: NodePrefix,
24 ) -> Result<ChangelogEntry, RevlogError> {
24 ) -> Result<ChangelogEntry, RevlogError> {
25 let rev = self.revlog.get_node_rev(node)?;
25 let rev = self.revlog.get_node_rev(node)?;
26 self.get_rev(rev)
26 self.get_rev(rev)
27 }
27 }
28
28
29 /// Return the `ChangelogEntry` of a given node revision.
29 /// Return the `ChangelogEntry` of a given node revision.
30 pub fn get_rev(
30 pub fn get_rev(
31 &self,
31 &self,
32 rev: Revision,
32 rev: Revision,
33 ) -> Result<ChangelogEntry, RevlogError> {
33 ) -> Result<ChangelogEntry, RevlogError> {
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.
40 #[derive(Debug)]
44 #[derive(Debug)]
41 pub struct ChangelogEntry {
45 pub struct ChangelogEntry {
42 /// The data bytes of the `changelog` entry.
46 /// The data bytes of the `changelog` entry.
43 bytes: Vec<u8>,
47 bytes: Vec<u8>,
44 }
48 }
45
49
46 impl ChangelogEntry {
50 impl ChangelogEntry {
47 /// Return an iterator over the lines of the entry.
51 /// Return an iterator over the lines of the entry.
48 pub fn lines(&self) -> impl Iterator<Item = &[u8]> {
52 pub fn lines(&self) -> impl Iterator<Item = &[u8]> {
49 self.bytes
53 self.bytes
50 .split(|b| b == &b'\n')
54 .split(|b| b == &b'\n')
51 .filter(|line| !line.is_empty())
55 .filter(|line| !line.is_empty())
52 }
56 }
53
57
54 /// Return the node id of the `manifest` referenced by this `changelog`
58 /// Return the node id of the `manifest` referenced by this `changelog`
55 /// entry.
59 /// entry.
56 pub fn manifest_node(&self) -> Result<&[u8], RevlogError> {
60 pub fn manifest_node(&self) -> Result<&[u8], RevlogError> {
57 self.lines()
61 self.lines()
58 .next()
62 .next()
59 .ok_or_else(|| HgError::corrupted("empty changelog entry").into())
63 .ok_or_else(|| HgError::corrupted("empty changelog entry").into())
60 }
64 }
61 }
65 }
@@ -1,405 +1,415 b''
1 // Copyright 2019-2020 Georges Racinet <georges.racinet@octobus.net>
1 // Copyright 2019-2020 Georges Racinet <georges.racinet@octobus.net>
2 //
2 //
3 // This software may be used and distributed according to the terms of the
3 // This software may be used and distributed according to the terms of the
4 // GNU General Public License version 2 or any later version.
4 // GNU General Public License version 2 or any later version.
5
5
6 //! Definitions and utilities for Revision nodes
6 //! Definitions and utilities for Revision nodes
7 //!
7 //!
8 //! In Mercurial code base, it is customary to call "a node" the binary SHA
8 //! In Mercurial code base, it is customary to call "a node" the binary SHA
9 //! of a revision.
9 //! of a revision.
10
10
11 use crate::errors::HgError;
11 use crate::errors::HgError;
12 use bytes_cast::BytesCast;
12 use bytes_cast::BytesCast;
13 use std::convert::{TryFrom, TryInto};
13 use std::convert::{TryFrom, TryInto};
14 use std::fmt;
14 use std::fmt;
15
15
16 /// The length in bytes of a `Node`
16 /// The length in bytes of a `Node`
17 ///
17 ///
18 /// This constant is meant to ease refactors of this module, and
18 /// This constant is meant to ease refactors of this module, and
19 /// are private so that calling code does not expect all nodes have
19 /// are private so that calling code does not expect all nodes have
20 /// the same size, should we support several formats concurrently in
20 /// the same size, should we support several formats concurrently in
21 /// the future.
21 /// the future.
22 pub const NODE_BYTES_LENGTH: usize = 20;
22 pub const NODE_BYTES_LENGTH: usize = 20;
23
23
24 /// Id of the null node.
24 /// Id of the null node.
25 ///
25 ///
26 /// Used to indicate the absence of node.
26 /// Used to indicate the absence of node.
27 pub const NULL_NODE_ID: [u8; NODE_BYTES_LENGTH] = [0u8; NODE_BYTES_LENGTH];
27 pub const NULL_NODE_ID: [u8; NODE_BYTES_LENGTH] = [0u8; NODE_BYTES_LENGTH];
28
28
29 /// The length in bytes of a `Node`
29 /// The length in bytes of a `Node`
30 ///
30 ///
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
37 /// Binary revision SHA
40 /// Binary revision SHA
38 ///
41 ///
39 /// ## Future changes of hash size
42 /// ## Future changes of hash size
40 ///
43 ///
41 /// To accomodate future changes of hash size, Rust callers
44 /// To accomodate future changes of hash size, Rust callers
42 /// should use the conversion methods at the boundaries (FFI, actual
45 /// should use the conversion methods at the boundaries (FFI, actual
43 /// computation of hashes and I/O) only, and only if required.
46 /// computation of hashes and I/O) only, and only if required.
44 ///
47 ///
45 /// All other callers outside of unit tests should just handle `Node` values
48 /// All other callers outside of unit tests should just handle `Node` values
46 /// and never make any assumption on the actual length, using [`nybbles_len`]
49 /// and never make any assumption on the actual length, using [`nybbles_len`]
47 /// if they need a loop boundary.
50 /// if they need a loop boundary.
48 ///
51 ///
49 /// All methods that create a `Node` either take a type that enforces
52 /// All methods that create a `Node` either take a type that enforces
50 /// the size or return an error at runtime.
53 /// the size or return an error at runtime.
51 ///
54 ///
52 /// [`nybbles_len`]: #method.nybbles_len
55 /// [`nybbles_len`]: #method.nybbles_len
53 #[derive(Copy, Clone, Debug, PartialEq, BytesCast, derive_more::From)]
56 #[derive(Copy, Clone, Debug, PartialEq, BytesCast, derive_more::From)]
54 #[repr(transparent)]
57 #[repr(transparent)]
55 pub struct Node {
58 pub struct Node {
56 data: NodeData,
59 data: NodeData,
57 }
60 }
58
61
59 /// The node value for NULL_REVISION
62 /// The node value for NULL_REVISION
60 pub const NULL_NODE: Node = Node {
63 pub const NULL_NODE: Node = Node {
61 data: [0; NODE_BYTES_LENGTH],
64 data: [0; NODE_BYTES_LENGTH],
62 };
65 };
63
66
64 /// Return an error if the slice has an unexpected length
67 /// Return an error if the slice has an unexpected length
65 impl<'a> TryFrom<&'a [u8]> for &'a Node {
68 impl<'a> TryFrom<&'a [u8]> for &'a Node {
66 type Error = ();
69 type Error = ();
67
70
68 #[inline]
71 #[inline]
69 fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> {
72 fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> {
70 match Node::from_bytes(bytes) {
73 match Node::from_bytes(bytes) {
71 Ok((node, rest)) if rest.is_empty() => Ok(node),
74 Ok((node, rest)) if rest.is_empty() => Ok(node),
72 _ => Err(()),
75 _ => Err(()),
73 }
76 }
74 }
77 }
75 }
78 }
76
79
77 /// Return an error if the slice has an unexpected length
80 /// Return an error if the slice has an unexpected length
78 impl TryFrom<&'_ [u8]> for Node {
81 impl TryFrom<&'_ [u8]> for Node {
79 type Error = std::array::TryFromSliceError;
82 type Error = std::array::TryFromSliceError;
80
83
81 #[inline]
84 #[inline]
82 fn try_from(bytes: &'_ [u8]) -> Result<Self, Self::Error> {
85 fn try_from(bytes: &'_ [u8]) -> Result<Self, Self::Error> {
83 let data = bytes.try_into()?;
86 let data = bytes.try_into()?;
84 Ok(Self { data })
87 Ok(Self { data })
85 }
88 }
86 }
89 }
87
90
88 impl From<&'_ NodeData> for Node {
91 impl From<&'_ NodeData> for Node {
89 #[inline]
92 #[inline]
90 fn from(data: &'_ NodeData) -> Self {
93 fn from(data: &'_ NodeData) -> Self {
91 Self { data: *data }
94 Self { data: *data }
92 }
95 }
93 }
96 }
94
97
95 impl fmt::LowerHex for Node {
98 impl fmt::LowerHex for Node {
96 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
99 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
97 for &byte in &self.data {
100 for &byte in &self.data {
98 write!(f, "{:02x}", byte)?
101 write!(f, "{:02x}", byte)?
99 }
102 }
100 Ok(())
103 Ok(())
101 }
104 }
102 }
105 }
103
106
104 #[derive(Debug)]
107 #[derive(Debug)]
105 pub struct FromHexError;
108 pub struct FromHexError;
106
109
107 /// Low level utility function, also for prefixes
110 /// Low level utility function, also for prefixes
108 fn get_nybble(s: &[u8], i: usize) -> u8 {
111 fn get_nybble(s: &[u8], i: usize) -> u8 {
109 if i % 2 == 0 {
112 if i % 2 == 0 {
110 s[i / 2] >> 4
113 s[i / 2] >> 4
111 } else {
114 } else {
112 s[i / 2] & 0x0f
115 s[i / 2] & 0x0f
113 }
116 }
114 }
117 }
115
118
116 impl Node {
119 impl Node {
117 /// Retrieve the `i`th half-byte of the binary data.
120 /// Retrieve the `i`th half-byte of the binary data.
118 ///
121 ///
119 /// This is also the `i`th hexadecimal digit in numeric form,
122 /// This is also the `i`th hexadecimal digit in numeric form,
120 /// also called a [nybble](https://en.wikipedia.org/wiki/Nibble).
123 /// also called a [nybble](https://en.wikipedia.org/wiki/Nibble).
121 pub fn get_nybble(&self, i: usize) -> u8 {
124 pub fn get_nybble(&self, i: usize) -> u8 {
122 get_nybble(&self.data, i)
125 get_nybble(&self.data, i)
123 }
126 }
124
127
125 /// Length of the data, in nybbles
128 /// Length of the data, in nybbles
126 pub fn nybbles_len(&self) -> usize {
129 pub fn nybbles_len(&self) -> usize {
127 // public exposure as an instance method only, so that we can
130 // public exposure as an instance method only, so that we can
128 // easily support several sizes of hashes if needed in the future.
131 // easily support several sizes of hashes if needed in the future.
129 NODE_NYBBLES_LENGTH
132 NODE_NYBBLES_LENGTH
130 }
133 }
131
134
132 /// Convert from hexadecimal string representation
135 /// Convert from hexadecimal string representation
133 ///
136 ///
134 /// Exact length is required.
137 /// Exact length is required.
135 ///
138 ///
136 /// To be used in FFI and I/O only, in order to facilitate future
139 /// To be used in FFI and I/O only, in order to facilitate future
137 /// changes of hash format.
140 /// changes of hash format.
138 pub fn from_hex(hex: impl AsRef<[u8]>) -> Result<Node, FromHexError> {
141 pub fn from_hex(hex: impl AsRef<[u8]>) -> Result<Node, FromHexError> {
139 let prefix = NodePrefix::from_hex(hex)?;
142 let prefix = NodePrefix::from_hex(hex)?;
140 if prefix.nybbles_len() == NODE_NYBBLES_LENGTH {
143 if prefix.nybbles_len() == NODE_NYBBLES_LENGTH {
141 Ok(Self { data: prefix.data })
144 Ok(Self { data: prefix.data })
142 } else {
145 } else {
143 Err(FromHexError)
146 Err(FromHexError)
144 }
147 }
145 }
148 }
146
149
147 /// `from_hex`, but for input from an internal file of the repository such
150 /// `from_hex`, but for input from an internal file of the repository such
148 /// as a changelog or manifest entry.
151 /// as a changelog or manifest entry.
149 ///
152 ///
150 /// An error is treated as repository corruption.
153 /// An error is treated as repository corruption.
151 pub fn from_hex_for_repo(hex: impl AsRef<[u8]>) -> Result<Node, HgError> {
154 pub fn from_hex_for_repo(hex: impl AsRef<[u8]>) -> Result<Node, HgError> {
152 Self::from_hex(hex.as_ref()).map_err(|FromHexError| {
155 Self::from_hex(hex.as_ref()).map_err(|FromHexError| {
153 HgError::CorruptedRepository(format!(
156 HgError::CorruptedRepository(format!(
154 "Expected a full hexadecimal node ID, found {}",
157 "Expected a full hexadecimal node ID, found {}",
155 String::from_utf8_lossy(hex.as_ref())
158 String::from_utf8_lossy(hex.as_ref())
156 ))
159 ))
157 })
160 })
158 }
161 }
159
162
160 /// Provide access to binary data
163 /// Provide access to binary data
161 ///
164 ///
162 /// This is needed by FFI layers, for instance to return expected
165 /// This is needed by FFI layers, for instance to return expected
163 /// binary values to Python.
166 /// binary values to Python.
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.
170 ///
180 ///
171 /// Since it can potentially come from an hexadecimal representation with
181 /// Since it can potentially come from an hexadecimal representation with
172 /// odd length, it needs to carry around whether the last 4 bits are relevant
182 /// odd length, it needs to carry around whether the last 4 bits are relevant
173 /// or not.
183 /// or not.
174 #[derive(Debug, PartialEq, Copy, Clone)]
184 #[derive(Debug, PartialEq, Copy, Clone)]
175 pub struct NodePrefix {
185 pub struct NodePrefix {
176 /// In `1..=NODE_NYBBLES_LENGTH`
186 /// In `1..=NODE_NYBBLES_LENGTH`
177 nybbles_len: u8,
187 nybbles_len: u8,
178 /// The first `4 * length_in_nybbles` bits are used (considering bits
188 /// The first `4 * length_in_nybbles` bits are used (considering bits
179 /// within a bytes in big-endian: most significant first), the rest
189 /// within a bytes in big-endian: most significant first), the rest
180 /// are zero.
190 /// are zero.
181 data: NodeData,
191 data: NodeData,
182 }
192 }
183
193
184 impl NodePrefix {
194 impl NodePrefix {
185 /// Convert from hexadecimal string representation
195 /// Convert from hexadecimal string representation
186 ///
196 ///
187 /// Similarly to `hex::decode`, can be used with Unicode string types
197 /// Similarly to `hex::decode`, can be used with Unicode string types
188 /// (`String`, `&str`) as well as bytes.
198 /// (`String`, `&str`) as well as bytes.
189 ///
199 ///
190 /// To be used in FFI and I/O only, in order to facilitate future
200 /// To be used in FFI and I/O only, in order to facilitate future
191 /// changes of hash format.
201 /// changes of hash format.
192 pub fn from_hex(hex: impl AsRef<[u8]>) -> Result<Self, FromHexError> {
202 pub fn from_hex(hex: impl AsRef<[u8]>) -> Result<Self, FromHexError> {
193 let hex = hex.as_ref();
203 let hex = hex.as_ref();
194 let len = hex.len();
204 let len = hex.len();
195 if len > NODE_NYBBLES_LENGTH || len == 0 {
205 if len > NODE_NYBBLES_LENGTH || len == 0 {
196 return Err(FromHexError);
206 return Err(FromHexError);
197 }
207 }
198
208
199 let mut data = [0; NODE_BYTES_LENGTH];
209 let mut data = [0; NODE_BYTES_LENGTH];
200 let mut nybbles_len = 0;
210 let mut nybbles_len = 0;
201 for &ascii_byte in hex {
211 for &ascii_byte in hex {
202 let nybble = match char::from(ascii_byte).to_digit(16) {
212 let nybble = match char::from(ascii_byte).to_digit(16) {
203 Some(digit) => digit as u8,
213 Some(digit) => digit as u8,
204 None => return Err(FromHexError),
214 None => return Err(FromHexError),
205 };
215 };
206 // Fill in the upper half of a byte first, then the lower half.
216 // Fill in the upper half of a byte first, then the lower half.
207 let shift = if nybbles_len % 2 == 0 { 4 } else { 0 };
217 let shift = if nybbles_len % 2 == 0 { 4 } else { 0 };
208 data[nybbles_len as usize / 2] |= nybble << shift;
218 data[nybbles_len as usize / 2] |= nybble << shift;
209 nybbles_len += 1;
219 nybbles_len += 1;
210 }
220 }
211 Ok(Self { data, nybbles_len })
221 Ok(Self { data, nybbles_len })
212 }
222 }
213
223
214 pub fn nybbles_len(&self) -> usize {
224 pub fn nybbles_len(&self) -> usize {
215 self.nybbles_len as _
225 self.nybbles_len as _
216 }
226 }
217
227
218 pub fn is_prefix_of(&self, node: &Node) -> bool {
228 pub fn is_prefix_of(&self, node: &Node) -> bool {
219 let full_bytes = self.nybbles_len() / 2;
229 let full_bytes = self.nybbles_len() / 2;
220 if self.data[..full_bytes] != node.data[..full_bytes] {
230 if self.data[..full_bytes] != node.data[..full_bytes] {
221 return false;
231 return false;
222 }
232 }
223 if self.nybbles_len() % 2 == 0 {
233 if self.nybbles_len() % 2 == 0 {
224 return true;
234 return true;
225 }
235 }
226 let last = self.nybbles_len() - 1;
236 let last = self.nybbles_len() - 1;
227 self.get_nybble(last) == node.get_nybble(last)
237 self.get_nybble(last) == node.get_nybble(last)
228 }
238 }
229
239
230 /// Retrieve the `i`th half-byte from the prefix.
240 /// Retrieve the `i`th half-byte from the prefix.
231 ///
241 ///
232 /// This is also the `i`th hexadecimal digit in numeric form,
242 /// This is also the `i`th hexadecimal digit in numeric form,
233 /// also called a [nybble](https://en.wikipedia.org/wiki/Nibble).
243 /// also called a [nybble](https://en.wikipedia.org/wiki/Nibble).
234 pub fn get_nybble(&self, i: usize) -> u8 {
244 pub fn get_nybble(&self, i: usize) -> u8 {
235 assert!(i < self.nybbles_len());
245 assert!(i < self.nybbles_len());
236 get_nybble(&self.data, i)
246 get_nybble(&self.data, i)
237 }
247 }
238
248
239 fn iter_nybbles(&self) -> impl Iterator<Item = u8> + '_ {
249 fn iter_nybbles(&self) -> impl Iterator<Item = u8> + '_ {
240 (0..self.nybbles_len()).map(move |i| get_nybble(&self.data, i))
250 (0..self.nybbles_len()).map(move |i| get_nybble(&self.data, i))
241 }
251 }
242
252
243 /// Return the index first nybble that's different from `node`
253 /// Return the index first nybble that's different from `node`
244 ///
254 ///
245 /// If the return value is `None` that means that `self` is
255 /// If the return value is `None` that means that `self` is
246 /// a prefix of `node`, but the current method is a bit slower
256 /// a prefix of `node`, but the current method is a bit slower
247 /// than `is_prefix_of`.
257 /// than `is_prefix_of`.
248 ///
258 ///
249 /// Returned index is as in `get_nybble`, i.e., starting at 0.
259 /// Returned index is as in `get_nybble`, i.e., starting at 0.
250 pub fn first_different_nybble(&self, node: &Node) -> Option<usize> {
260 pub fn first_different_nybble(&self, node: &Node) -> Option<usize> {
251 self.iter_nybbles()
261 self.iter_nybbles()
252 .zip(NodePrefix::from(*node).iter_nybbles())
262 .zip(NodePrefix::from(*node).iter_nybbles())
253 .position(|(a, b)| a != b)
263 .position(|(a, b)| a != b)
254 }
264 }
255 }
265 }
256
266
257 impl fmt::LowerHex for NodePrefix {
267 impl fmt::LowerHex for NodePrefix {
258 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
268 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
259 let full_bytes = self.nybbles_len() / 2;
269 let full_bytes = self.nybbles_len() / 2;
260 for &byte in &self.data[..full_bytes] {
270 for &byte in &self.data[..full_bytes] {
261 write!(f, "{:02x}", byte)?
271 write!(f, "{:02x}", byte)?
262 }
272 }
263 if self.nybbles_len() % 2 == 1 {
273 if self.nybbles_len() % 2 == 1 {
264 let last = self.nybbles_len() - 1;
274 let last = self.nybbles_len() - 1;
265 write!(f, "{:x}", self.get_nybble(last))?
275 write!(f, "{:x}", self.get_nybble(last))?
266 }
276 }
267 Ok(())
277 Ok(())
268 }
278 }
269 }
279 }
270
280
271 /// A shortcut for full `Node` references
281 /// A shortcut for full `Node` references
272 impl From<&'_ Node> for NodePrefix {
282 impl From<&'_ Node> for NodePrefix {
273 fn from(node: &'_ Node) -> Self {
283 fn from(node: &'_ Node) -> Self {
274 NodePrefix {
284 NodePrefix {
275 nybbles_len: node.nybbles_len() as _,
285 nybbles_len: node.nybbles_len() as _,
276 data: node.data,
286 data: node.data,
277 }
287 }
278 }
288 }
279 }
289 }
280
290
281 /// A shortcut for full `Node` references
291 /// A shortcut for full `Node` references
282 impl From<Node> for NodePrefix {
292 impl From<Node> for NodePrefix {
283 fn from(node: Node) -> Self {
293 fn from(node: Node) -> Self {
284 NodePrefix {
294 NodePrefix {
285 nybbles_len: node.nybbles_len() as _,
295 nybbles_len: node.nybbles_len() as _,
286 data: node.data,
296 data: node.data,
287 }
297 }
288 }
298 }
289 }
299 }
290
300
291 impl PartialEq<Node> for NodePrefix {
301 impl PartialEq<Node> for NodePrefix {
292 fn eq(&self, other: &Node) -> bool {
302 fn eq(&self, other: &Node) -> bool {
293 Self::from(*other) == *self
303 Self::from(*other) == *self
294 }
304 }
295 }
305 }
296
306
297 #[cfg(test)]
307 #[cfg(test)]
298 mod tests {
308 mod tests {
299 use super::*;
309 use super::*;
300
310
301 const SAMPLE_NODE_HEX: &str = "0123456789abcdeffedcba9876543210deadbeef";
311 const SAMPLE_NODE_HEX: &str = "0123456789abcdeffedcba9876543210deadbeef";
302 const SAMPLE_NODE: Node = Node {
312 const SAMPLE_NODE: Node = Node {
303 data: [
313 data: [
304 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba,
314 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba,
305 0x98, 0x76, 0x54, 0x32, 0x10, 0xde, 0xad, 0xbe, 0xef,
315 0x98, 0x76, 0x54, 0x32, 0x10, 0xde, 0xad, 0xbe, 0xef,
306 ],
316 ],
307 };
317 };
308
318
309 /// Pad an hexadecimal string to reach `NODE_NYBBLES_LENGTH`
319 /// Pad an hexadecimal string to reach `NODE_NYBBLES_LENGTH`
310 /// The padding is made with zeros.
320 /// The padding is made with zeros.
311 pub fn hex_pad_right(hex: &str) -> String {
321 pub fn hex_pad_right(hex: &str) -> String {
312 let mut res = hex.to_string();
322 let mut res = hex.to_string();
313 while res.len() < NODE_NYBBLES_LENGTH {
323 while res.len() < NODE_NYBBLES_LENGTH {
314 res.push('0');
324 res.push('0');
315 }
325 }
316 res
326 res
317 }
327 }
318
328
319 #[test]
329 #[test]
320 fn test_node_from_hex() {
330 fn test_node_from_hex() {
321 let not_hex = "012... oops";
331 let not_hex = "012... oops";
322 let too_short = "0123";
332 let too_short = "0123";
323 let too_long = format!("{}0", SAMPLE_NODE_HEX);
333 let too_long = format!("{}0", SAMPLE_NODE_HEX);
324 assert_eq!(Node::from_hex(SAMPLE_NODE_HEX).unwrap(), SAMPLE_NODE);
334 assert_eq!(Node::from_hex(SAMPLE_NODE_HEX).unwrap(), SAMPLE_NODE);
325 assert!(Node::from_hex(not_hex).is_err());
335 assert!(Node::from_hex(not_hex).is_err());
326 assert!(Node::from_hex(too_short).is_err());
336 assert!(Node::from_hex(too_short).is_err());
327 assert!(Node::from_hex(&too_long).is_err());
337 assert!(Node::from_hex(&too_long).is_err());
328 }
338 }
329
339
330 #[test]
340 #[test]
331 fn test_node_encode_hex() {
341 fn test_node_encode_hex() {
332 assert_eq!(format!("{:x}", SAMPLE_NODE), SAMPLE_NODE_HEX);
342 assert_eq!(format!("{:x}", SAMPLE_NODE), SAMPLE_NODE_HEX);
333 }
343 }
334
344
335 #[test]
345 #[test]
336 fn test_prefix_from_to_hex() -> Result<(), FromHexError> {
346 fn test_prefix_from_to_hex() -> Result<(), FromHexError> {
337 assert_eq!(format!("{:x}", NodePrefix::from_hex("0e1")?), "0e1");
347 assert_eq!(format!("{:x}", NodePrefix::from_hex("0e1")?), "0e1");
338 assert_eq!(format!("{:x}", NodePrefix::from_hex("0e1a")?), "0e1a");
348 assert_eq!(format!("{:x}", NodePrefix::from_hex("0e1a")?), "0e1a");
339 assert_eq!(
349 assert_eq!(
340 format!("{:x}", NodePrefix::from_hex(SAMPLE_NODE_HEX)?),
350 format!("{:x}", NodePrefix::from_hex(SAMPLE_NODE_HEX)?),
341 SAMPLE_NODE_HEX
351 SAMPLE_NODE_HEX
342 );
352 );
343 Ok(())
353 Ok(())
344 }
354 }
345
355
346 #[test]
356 #[test]
347 fn test_prefix_from_hex_errors() {
357 fn test_prefix_from_hex_errors() {
348 assert!(NodePrefix::from_hex("testgr").is_err());
358 assert!(NodePrefix::from_hex("testgr").is_err());
349 let mut long = format!("{:x}", NULL_NODE);
359 let mut long = format!("{:x}", NULL_NODE);
350 long.push('c');
360 long.push('c');
351 assert!(NodePrefix::from_hex(&long).is_err())
361 assert!(NodePrefix::from_hex(&long).is_err())
352 }
362 }
353
363
354 #[test]
364 #[test]
355 fn test_is_prefix_of() -> Result<(), FromHexError> {
365 fn test_is_prefix_of() -> Result<(), FromHexError> {
356 let mut node_data = [0; NODE_BYTES_LENGTH];
366 let mut node_data = [0; NODE_BYTES_LENGTH];
357 node_data[0] = 0x12;
367 node_data[0] = 0x12;
358 node_data[1] = 0xca;
368 node_data[1] = 0xca;
359 let node = Node::from(node_data);
369 let node = Node::from(node_data);
360 assert!(NodePrefix::from_hex("12")?.is_prefix_of(&node));
370 assert!(NodePrefix::from_hex("12")?.is_prefix_of(&node));
361 assert!(!NodePrefix::from_hex("1a")?.is_prefix_of(&node));
371 assert!(!NodePrefix::from_hex("1a")?.is_prefix_of(&node));
362 assert!(NodePrefix::from_hex("12c")?.is_prefix_of(&node));
372 assert!(NodePrefix::from_hex("12c")?.is_prefix_of(&node));
363 assert!(!NodePrefix::from_hex("12d")?.is_prefix_of(&node));
373 assert!(!NodePrefix::from_hex("12d")?.is_prefix_of(&node));
364 Ok(())
374 Ok(())
365 }
375 }
366
376
367 #[test]
377 #[test]
368 fn test_get_nybble() -> Result<(), FromHexError> {
378 fn test_get_nybble() -> Result<(), FromHexError> {
369 let prefix = NodePrefix::from_hex("dead6789cafe")?;
379 let prefix = NodePrefix::from_hex("dead6789cafe")?;
370 assert_eq!(prefix.get_nybble(0), 13);
380 assert_eq!(prefix.get_nybble(0), 13);
371 assert_eq!(prefix.get_nybble(7), 9);
381 assert_eq!(prefix.get_nybble(7), 9);
372 Ok(())
382 Ok(())
373 }
383 }
374
384
375 #[test]
385 #[test]
376 fn test_first_different_nybble_even_prefix() {
386 fn test_first_different_nybble_even_prefix() {
377 let prefix = NodePrefix::from_hex("12ca").unwrap();
387 let prefix = NodePrefix::from_hex("12ca").unwrap();
378 let mut node = Node::from([0; NODE_BYTES_LENGTH]);
388 let mut node = Node::from([0; NODE_BYTES_LENGTH]);
379 assert_eq!(prefix.first_different_nybble(&node), Some(0));
389 assert_eq!(prefix.first_different_nybble(&node), Some(0));
380 node.data[0] = 0x13;
390 node.data[0] = 0x13;
381 assert_eq!(prefix.first_different_nybble(&node), Some(1));
391 assert_eq!(prefix.first_different_nybble(&node), Some(1));
382 node.data[0] = 0x12;
392 node.data[0] = 0x12;
383 assert_eq!(prefix.first_different_nybble(&node), Some(2));
393 assert_eq!(prefix.first_different_nybble(&node), Some(2));
384 node.data[1] = 0xca;
394 node.data[1] = 0xca;
385 // now it is a prefix
395 // now it is a prefix
386 assert_eq!(prefix.first_different_nybble(&node), None);
396 assert_eq!(prefix.first_different_nybble(&node), None);
387 }
397 }
388
398
389 #[test]
399 #[test]
390 fn test_first_different_nybble_odd_prefix() {
400 fn test_first_different_nybble_odd_prefix() {
391 let prefix = NodePrefix::from_hex("12c").unwrap();
401 let prefix = NodePrefix::from_hex("12c").unwrap();
392 let mut node = Node::from([0; NODE_BYTES_LENGTH]);
402 let mut node = Node::from([0; NODE_BYTES_LENGTH]);
393 assert_eq!(prefix.first_different_nybble(&node), Some(0));
403 assert_eq!(prefix.first_different_nybble(&node), Some(0));
394 node.data[0] = 0x13;
404 node.data[0] = 0x13;
395 assert_eq!(prefix.first_different_nybble(&node), Some(1));
405 assert_eq!(prefix.first_different_nybble(&node), Some(1));
396 node.data[0] = 0x12;
406 node.data[0] = 0x12;
397 assert_eq!(prefix.first_different_nybble(&node), Some(2));
407 assert_eq!(prefix.first_different_nybble(&node), Some(2));
398 node.data[1] = 0xca;
408 node.data[1] = 0xca;
399 // now it is a prefix
409 // now it is a prefix
400 assert_eq!(prefix.first_different_nybble(&node), None);
410 assert_eq!(prefix.first_different_nybble(&node), None);
401 }
411 }
402 }
412 }
403
413
404 #[cfg(test)]
414 #[cfg(test)]
405 pub use tests::hex_pad_right;
415 pub use tests::hex_pad_right;
@@ -1,393 +1,393 b''
1 use std::borrow::Cow;
1 use std::borrow::Cow;
2 use std::io::Read;
2 use std::io::Read;
3 use std::ops::Deref;
3 use std::ops::Deref;
4 use std::path::Path;
4 use std::path::Path;
5
5
6 use byteorder::{BigEndian, ByteOrder};
6 use byteorder::{BigEndian, ByteOrder};
7 use crypto::digest::Digest;
7 use crypto::digest::Digest;
8 use crypto::sha1::Sha1;
8 use crypto::sha1::Sha1;
9 use flate2::read::ZlibDecoder;
9 use flate2::read::ZlibDecoder;
10 use micro_timer::timed;
10 use micro_timer::timed;
11 use zstd;
11 use zstd;
12
12
13 use super::index::Index;
13 use super::index::Index;
14 use super::node::{NodePrefix, NODE_BYTES_LENGTH, NULL_NODE};
14 use super::node::{NodePrefix, NODE_BYTES_LENGTH, NULL_NODE};
15 use super::nodemap;
15 use super::nodemap;
16 use super::nodemap::{NodeMap, NodeMapError};
16 use super::nodemap::{NodeMap, NodeMapError};
17 use super::nodemap_docket::NodeMapDocket;
17 use super::nodemap_docket::NodeMapDocket;
18 use super::patch;
18 use super::patch;
19 use crate::errors::HgError;
19 use crate::errors::HgError;
20 use crate::repo::Repo;
20 use crate::repo::Repo;
21 use crate::revlog::Revision;
21 use crate::revlog::Revision;
22
22
23 #[derive(derive_more::From)]
23 #[derive(derive_more::From)]
24 pub enum RevlogError {
24 pub enum RevlogError {
25 InvalidRevision,
25 InvalidRevision,
26 /// Found more than one entry whose ID match the requested prefix
26 /// Found more than one entry whose ID match the requested prefix
27 AmbiguousPrefix,
27 AmbiguousPrefix,
28 #[from]
28 #[from]
29 Other(HgError),
29 Other(HgError),
30 }
30 }
31
31
32 impl From<NodeMapError> for RevlogError {
32 impl From<NodeMapError> for RevlogError {
33 fn from(error: NodeMapError) -> Self {
33 fn from(error: NodeMapError) -> Self {
34 match error {
34 match error {
35 NodeMapError::MultipleResults => RevlogError::AmbiguousPrefix,
35 NodeMapError::MultipleResults => RevlogError::AmbiguousPrefix,
36 NodeMapError::RevisionNotInIndex(_) => RevlogError::corrupted(),
36 NodeMapError::RevisionNotInIndex(_) => RevlogError::corrupted(),
37 }
37 }
38 }
38 }
39 }
39 }
40
40
41 impl RevlogError {
41 impl RevlogError {
42 fn corrupted() -> Self {
42 fn corrupted() -> Self {
43 RevlogError::Other(HgError::corrupted("corrupted revlog"))
43 RevlogError::Other(HgError::corrupted("corrupted revlog"))
44 }
44 }
45 }
45 }
46
46
47 /// Read only implementation of revlog.
47 /// Read only implementation of revlog.
48 pub struct Revlog {
48 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
56 nodemap: Option<nodemap::NodeTree>,
56 nodemap: Option<nodemap::NodeTree>,
57 }
57 }
58
58
59 impl Revlog {
59 impl Revlog {
60 /// Open a revlog index file.
60 /// Open a revlog index file.
61 ///
61 ///
62 /// It will also open the associated data file if index and data are not
62 /// It will also open the associated data file if index and data are not
63 /// interleaved.
63 /// interleaved.
64 #[timed]
64 #[timed]
65 pub fn open(
65 pub fn open(
66 repo: &Repo,
66 repo: &Repo,
67 index_path: impl AsRef<Path>,
67 index_path: impl AsRef<Path>,
68 data_path: Option<&Path>,
68 data_path: Option<&Path>,
69 ) -> Result<Self, RevlogError> {
69 ) -> Result<Self, RevlogError> {
70 let index_path = index_path.as_ref();
70 let index_path = index_path.as_ref();
71 let index_mmap = repo.store_vfs().mmap_open(&index_path)?;
71 let index_mmap = repo.store_vfs().mmap_open(&index_path)?;
72
72
73 let version = get_version(&index_mmap);
73 let version = get_version(&index_mmap);
74 if version != 1 {
74 if version != 1 {
75 // A proper new version should have had a repo/store requirement.
75 // A proper new version should have had a repo/store requirement.
76 return Err(RevlogError::corrupted());
76 return Err(RevlogError::corrupted());
77 }
77 }
78
78
79 let index = Index::new(Box::new(index_mmap))?;
79 let index = Index::new(Box::new(index_mmap))?;
80
80
81 let default_data_path = index_path.with_extension("d");
81 let default_data_path = index_path.with_extension("d");
82
82
83 // type annotation required
83 // type annotation required
84 // won't recognize Mmap as Deref<Target = [u8]>
84 // won't recognize Mmap as Deref<Target = [u8]>
85 let data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>> =
85 let data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>> =
86 if index.is_inline() {
86 if index.is_inline() {
87 None
87 None
88 } else {
88 } else {
89 let data_path = data_path.unwrap_or(&default_data_path);
89 let data_path = data_path.unwrap_or(&default_data_path);
90 let data_mmap = repo.store_vfs().mmap_open(data_path)?;
90 let data_mmap = repo.store_vfs().mmap_open(data_path)?;
91 Some(Box::new(data_mmap))
91 Some(Box::new(data_mmap))
92 };
92 };
93
93
94 let nodemap = NodeMapDocket::read_from_file(repo, index_path)?.map(
94 let nodemap = NodeMapDocket::read_from_file(repo, index_path)?.map(
95 |(docket, data)| {
95 |(docket, data)| {
96 nodemap::NodeTree::load_bytes(
96 nodemap::NodeTree::load_bytes(
97 Box::new(data),
97 Box::new(data),
98 docket.data_length,
98 docket.data_length,
99 )
99 )
100 },
100 },
101 );
101 );
102
102
103 Ok(Revlog {
103 Ok(Revlog {
104 index,
104 index,
105 data_bytes,
105 data_bytes,
106 nodemap,
106 nodemap,
107 })
107 })
108 }
108 }
109
109
110 /// Return number of entries of the `Revlog`.
110 /// Return number of entries of the `Revlog`.
111 pub fn len(&self) -> usize {
111 pub fn len(&self) -> usize {
112 self.index.len()
112 self.index.len()
113 }
113 }
114
114
115 /// Returns `true` if the `Revlog` has zero `entries`.
115 /// Returns `true` if the `Revlog` has zero `entries`.
116 pub fn is_empty(&self) -> bool {
116 pub fn is_empty(&self) -> bool {
117 self.index.is_empty()
117 self.index.is_empty()
118 }
118 }
119
119
120 /// Return the full data associated to a node.
120 /// Return the full data associated to a node.
121 #[timed]
121 #[timed]
122 pub fn get_node_rev(
122 pub fn get_node_rev(
123 &self,
123 &self,
124 node: NodePrefix,
124 node: NodePrefix,
125 ) -> Result<Revision, RevlogError> {
125 ) -> Result<Revision, RevlogError> {
126 if let Some(nodemap) = &self.nodemap {
126 if let Some(nodemap) = &self.nodemap {
127 return nodemap
127 return nodemap
128 .find_bin(&self.index, node)?
128 .find_bin(&self.index, node)?
129 .ok_or(RevlogError::InvalidRevision);
129 .ok_or(RevlogError::InvalidRevision);
130 }
130 }
131
131
132 // Fallback to linear scan when a persistent nodemap is not present.
132 // Fallback to linear scan when a persistent nodemap is not present.
133 // This happens when the persistent-nodemap experimental feature is not
133 // This happens when the persistent-nodemap experimental feature is not
134 // enabled, or for small revlogs.
134 // enabled, or for small revlogs.
135 //
135 //
136 // TODO: consider building a non-persistent nodemap in memory to
136 // TODO: consider building a non-persistent nodemap in memory to
137 // optimize these cases.
137 // optimize these cases.
138 let mut found_by_prefix = None;
138 let mut found_by_prefix = None;
139 for rev in (0..self.len() as Revision).rev() {
139 for rev in (0..self.len() as Revision).rev() {
140 let index_entry =
140 let index_entry =
141 self.index.get_entry(rev).ok_or(HgError::corrupted(
141 self.index.get_entry(rev).ok_or(HgError::corrupted(
142 "revlog references a revision not in the index",
142 "revlog references a revision not in the index",
143 ))?;
143 ))?;
144 if node == *index_entry.hash() {
144 if node == *index_entry.hash() {
145 return Ok(rev);
145 return Ok(rev);
146 }
146 }
147 if node.is_prefix_of(index_entry.hash()) {
147 if node.is_prefix_of(index_entry.hash()) {
148 if found_by_prefix.is_some() {
148 if found_by_prefix.is_some() {
149 return Err(RevlogError::AmbiguousPrefix);
149 return Err(RevlogError::AmbiguousPrefix);
150 }
150 }
151 found_by_prefix = Some(rev)
151 found_by_prefix = Some(rev)
152 }
152 }
153 }
153 }
154 found_by_prefix.ok_or(RevlogError::InvalidRevision)
154 found_by_prefix.ok_or(RevlogError::InvalidRevision)
155 }
155 }
156
156
157 /// Returns whether the given revision exists in this revlog.
157 /// Returns whether the given revision exists in this revlog.
158 pub fn has_rev(&self, rev: Revision) -> bool {
158 pub fn has_rev(&self, rev: Revision) -> bool {
159 self.index.get_entry(rev).is_some()
159 self.index.get_entry(rev).is_some()
160 }
160 }
161
161
162 /// Return the full data associated to a revision.
162 /// Return the full data associated to a revision.
163 ///
163 ///
164 /// All entries required to build the final data out of deltas will be
164 /// All entries required to build the final data out of deltas will be
165 /// retrieved as needed, and the deltas will be applied to the inital
165 /// retrieved as needed, and the deltas will be applied to the inital
166 /// snapshot to rebuild the final data.
166 /// snapshot to rebuild the final data.
167 #[timed]
167 #[timed]
168 pub fn get_rev_data(&self, rev: Revision) -> Result<Vec<u8>, RevlogError> {
168 pub fn get_rev_data(&self, rev: Revision) -> Result<Vec<u8>, RevlogError> {
169 // Todo return -> Cow
169 // Todo return -> Cow
170 let mut entry = self.get_entry(rev)?;
170 let mut entry = self.get_entry(rev)?;
171 let mut delta_chain = vec![];
171 let mut delta_chain = vec![];
172 while let Some(base_rev) = entry.base_rev {
172 while let Some(base_rev) = entry.base_rev {
173 delta_chain.push(entry);
173 delta_chain.push(entry);
174 entry = self
174 entry = self
175 .get_entry(base_rev)
175 .get_entry(base_rev)
176 .map_err(|_| RevlogError::corrupted())?;
176 .map_err(|_| RevlogError::corrupted())?;
177 }
177 }
178
178
179 // TODO do not look twice in the index
179 // TODO do not look twice in the index
180 let index_entry = self
180 let index_entry = self
181 .index
181 .index
182 .get_entry(rev)
182 .get_entry(rev)
183 .ok_or(RevlogError::InvalidRevision)?;
183 .ok_or(RevlogError::InvalidRevision)?;
184
184
185 let data: Vec<u8> = if delta_chain.is_empty() {
185 let data: Vec<u8> = if delta_chain.is_empty() {
186 entry.data()?.into()
186 entry.data()?.into()
187 } else {
187 } else {
188 Revlog::build_data_from_deltas(entry, &delta_chain)?
188 Revlog::build_data_from_deltas(entry, &delta_chain)?
189 };
189 };
190
190
191 if self.check_hash(
191 if self.check_hash(
192 index_entry.p1(),
192 index_entry.p1(),
193 index_entry.p2(),
193 index_entry.p2(),
194 index_entry.hash().as_bytes(),
194 index_entry.hash().as_bytes(),
195 &data,
195 &data,
196 ) {
196 ) {
197 Ok(data)
197 Ok(data)
198 } else {
198 } else {
199 Err(RevlogError::corrupted())
199 Err(RevlogError::corrupted())
200 }
200 }
201 }
201 }
202
202
203 /// Check the hash of some given data against the recorded hash.
203 /// Check the hash of some given data against the recorded hash.
204 pub fn check_hash(
204 pub fn check_hash(
205 &self,
205 &self,
206 p1: Revision,
206 p1: Revision,
207 p2: Revision,
207 p2: Revision,
208 expected: &[u8],
208 expected: &[u8],
209 data: &[u8],
209 data: &[u8],
210 ) -> bool {
210 ) -> bool {
211 let e1 = self.index.get_entry(p1);
211 let e1 = self.index.get_entry(p1);
212 let h1 = match e1 {
212 let h1 = match e1 {
213 Some(ref entry) => entry.hash(),
213 Some(ref entry) => entry.hash(),
214 None => &NULL_NODE,
214 None => &NULL_NODE,
215 };
215 };
216 let e2 = self.index.get_entry(p2);
216 let e2 = self.index.get_entry(p2);
217 let h2 = match e2 {
217 let h2 = match e2 {
218 Some(ref entry) => entry.hash(),
218 Some(ref entry) => entry.hash(),
219 None => &NULL_NODE,
219 None => &NULL_NODE,
220 };
220 };
221
221
222 hash(data, h1.as_bytes(), h2.as_bytes()).as_slice() == expected
222 hash(data, h1.as_bytes(), h2.as_bytes()).as_slice() == expected
223 }
223 }
224
224
225 /// Build the full data of a revision out its snapshot
225 /// Build the full data of a revision out its snapshot
226 /// and its deltas.
226 /// and its deltas.
227 #[timed]
227 #[timed]
228 fn build_data_from_deltas(
228 fn build_data_from_deltas(
229 snapshot: RevlogEntry,
229 snapshot: RevlogEntry,
230 deltas: &[RevlogEntry],
230 deltas: &[RevlogEntry],
231 ) -> Result<Vec<u8>, RevlogError> {
231 ) -> Result<Vec<u8>, RevlogError> {
232 let snapshot = snapshot.data()?;
232 let snapshot = snapshot.data()?;
233 let deltas = deltas
233 let deltas = deltas
234 .iter()
234 .iter()
235 .rev()
235 .rev()
236 .map(RevlogEntry::data)
236 .map(RevlogEntry::data)
237 .collect::<Result<Vec<Cow<'_, [u8]>>, RevlogError>>()?;
237 .collect::<Result<Vec<Cow<'_, [u8]>>, RevlogError>>()?;
238 let patches: Vec<_> =
238 let patches: Vec<_> =
239 deltas.iter().map(|d| patch::PatchList::new(d)).collect();
239 deltas.iter().map(|d| patch::PatchList::new(d)).collect();
240 let patch = patch::fold_patch_lists(&patches);
240 let patch = patch::fold_patch_lists(&patches);
241 Ok(patch.apply(&snapshot))
241 Ok(patch.apply(&snapshot))
242 }
242 }
243
243
244 /// Return the revlog data.
244 /// Return the revlog data.
245 fn data(&self) -> &[u8] {
245 fn data(&self) -> &[u8] {
246 match self.data_bytes {
246 match self.data_bytes {
247 Some(ref data_bytes) => &data_bytes,
247 Some(ref data_bytes) => &data_bytes,
248 None => panic!(
248 None => panic!(
249 "forgot to load the data or trying to access inline data"
249 "forgot to load the data or trying to access inline data"
250 ),
250 ),
251 }
251 }
252 }
252 }
253
253
254 /// Get an entry of the revlog.
254 /// Get an entry of the revlog.
255 fn get_entry(&self, rev: Revision) -> Result<RevlogEntry, RevlogError> {
255 fn get_entry(&self, rev: Revision) -> Result<RevlogEntry, RevlogError> {
256 let index_entry = self
256 let index_entry = self
257 .index
257 .index
258 .get_entry(rev)
258 .get_entry(rev)
259 .ok_or(RevlogError::InvalidRevision)?;
259 .ok_or(RevlogError::InvalidRevision)?;
260 let start = index_entry.offset();
260 let start = index_entry.offset();
261 let end = start + index_entry.compressed_len();
261 let end = start + index_entry.compressed_len();
262 let data = if self.index.is_inline() {
262 let data = if self.index.is_inline() {
263 self.index.data(start, end)
263 self.index.data(start, end)
264 } else {
264 } else {
265 &self.data()[start..end]
265 &self.data()[start..end]
266 };
266 };
267 let entry = RevlogEntry {
267 let entry = RevlogEntry {
268 rev,
268 rev,
269 bytes: data,
269 bytes: data,
270 compressed_len: index_entry.compressed_len(),
270 compressed_len: index_entry.compressed_len(),
271 uncompressed_len: index_entry.uncompressed_len(),
271 uncompressed_len: index_entry.uncompressed_len(),
272 base_rev: if index_entry.base_revision() == rev {
272 base_rev: if index_entry.base_revision() == rev {
273 None
273 None
274 } else {
274 } else {
275 Some(index_entry.base_revision())
275 Some(index_entry.base_revision())
276 },
276 },
277 };
277 };
278 Ok(entry)
278 Ok(entry)
279 }
279 }
280 }
280 }
281
281
282 /// The revlog entry's bytes and the necessary informations to extract
282 /// The revlog entry's bytes and the necessary informations to extract
283 /// the entry's data.
283 /// the entry's data.
284 #[derive(Debug)]
284 #[derive(Debug)]
285 pub struct RevlogEntry<'a> {
285 pub struct RevlogEntry<'a> {
286 rev: Revision,
286 rev: Revision,
287 bytes: &'a [u8],
287 bytes: &'a [u8],
288 compressed_len: usize,
288 compressed_len: usize,
289 uncompressed_len: usize,
289 uncompressed_len: usize,
290 base_rev: Option<Revision>,
290 base_rev: Option<Revision>,
291 }
291 }
292
292
293 impl<'a> RevlogEntry<'a> {
293 impl<'a> RevlogEntry<'a> {
294 /// Extract the data contained in the entry.
294 /// Extract the data contained in the entry.
295 pub fn data(&self) -> Result<Cow<'_, [u8]>, RevlogError> {
295 pub fn data(&self) -> Result<Cow<'_, [u8]>, RevlogError> {
296 if self.bytes.is_empty() {
296 if self.bytes.is_empty() {
297 return Ok(Cow::Borrowed(&[]));
297 return Ok(Cow::Borrowed(&[]));
298 }
298 }
299 match self.bytes[0] {
299 match self.bytes[0] {
300 // Revision data is the entirety of the entry, including this
300 // Revision data is the entirety of the entry, including this
301 // header.
301 // header.
302 b'\0' => Ok(Cow::Borrowed(self.bytes)),
302 b'\0' => Ok(Cow::Borrowed(self.bytes)),
303 // Raw revision data follows.
303 // Raw revision data follows.
304 b'u' => Ok(Cow::Borrowed(&self.bytes[1..])),
304 b'u' => Ok(Cow::Borrowed(&self.bytes[1..])),
305 // zlib (RFC 1950) data.
305 // zlib (RFC 1950) data.
306 b'x' => Ok(Cow::Owned(self.uncompressed_zlib_data()?)),
306 b'x' => Ok(Cow::Owned(self.uncompressed_zlib_data()?)),
307 // zstd data.
307 // zstd data.
308 b'\x28' => Ok(Cow::Owned(self.uncompressed_zstd_data()?)),
308 b'\x28' => Ok(Cow::Owned(self.uncompressed_zstd_data()?)),
309 // A proper new format should have had a repo/store requirement.
309 // A proper new format should have had a repo/store requirement.
310 _format_type => Err(RevlogError::corrupted()),
310 _format_type => Err(RevlogError::corrupted()),
311 }
311 }
312 }
312 }
313
313
314 fn uncompressed_zlib_data(&self) -> Result<Vec<u8>, RevlogError> {
314 fn uncompressed_zlib_data(&self) -> Result<Vec<u8>, RevlogError> {
315 let mut decoder = ZlibDecoder::new(self.bytes);
315 let mut decoder = ZlibDecoder::new(self.bytes);
316 if self.is_delta() {
316 if self.is_delta() {
317 let mut buf = Vec::with_capacity(self.compressed_len);
317 let mut buf = Vec::with_capacity(self.compressed_len);
318 decoder
318 decoder
319 .read_to_end(&mut buf)
319 .read_to_end(&mut buf)
320 .map_err(|_| RevlogError::corrupted())?;
320 .map_err(|_| RevlogError::corrupted())?;
321 Ok(buf)
321 Ok(buf)
322 } else {
322 } else {
323 let mut buf = vec![0; self.uncompressed_len];
323 let mut buf = vec![0; self.uncompressed_len];
324 decoder
324 decoder
325 .read_exact(&mut buf)
325 .read_exact(&mut buf)
326 .map_err(|_| RevlogError::corrupted())?;
326 .map_err(|_| RevlogError::corrupted())?;
327 Ok(buf)
327 Ok(buf)
328 }
328 }
329 }
329 }
330
330
331 fn uncompressed_zstd_data(&self) -> Result<Vec<u8>, RevlogError> {
331 fn uncompressed_zstd_data(&self) -> Result<Vec<u8>, RevlogError> {
332 if self.is_delta() {
332 if self.is_delta() {
333 let mut buf = Vec::with_capacity(self.compressed_len);
333 let mut buf = Vec::with_capacity(self.compressed_len);
334 zstd::stream::copy_decode(self.bytes, &mut buf)
334 zstd::stream::copy_decode(self.bytes, &mut buf)
335 .map_err(|_| RevlogError::corrupted())?;
335 .map_err(|_| RevlogError::corrupted())?;
336 Ok(buf)
336 Ok(buf)
337 } else {
337 } else {
338 let mut buf = vec![0; self.uncompressed_len];
338 let mut buf = vec![0; self.uncompressed_len];
339 let len = zstd::block::decompress_to_buffer(self.bytes, &mut buf)
339 let len = zstd::block::decompress_to_buffer(self.bytes, &mut buf)
340 .map_err(|_| RevlogError::corrupted())?;
340 .map_err(|_| RevlogError::corrupted())?;
341 if len != self.uncompressed_len {
341 if len != self.uncompressed_len {
342 Err(RevlogError::corrupted())
342 Err(RevlogError::corrupted())
343 } else {
343 } else {
344 Ok(buf)
344 Ok(buf)
345 }
345 }
346 }
346 }
347 }
347 }
348
348
349 /// Tell if the entry is a snapshot or a delta
349 /// Tell if the entry is a snapshot or a delta
350 /// (influences on decompression).
350 /// (influences on decompression).
351 fn is_delta(&self) -> bool {
351 fn is_delta(&self) -> bool {
352 self.base_rev.is_some()
352 self.base_rev.is_some()
353 }
353 }
354 }
354 }
355
355
356 /// Format version of the revlog.
356 /// Format version of the revlog.
357 pub fn get_version(index_bytes: &[u8]) -> u16 {
357 pub fn get_version(index_bytes: &[u8]) -> u16 {
358 BigEndian::read_u16(&index_bytes[2..=3])
358 BigEndian::read_u16(&index_bytes[2..=3])
359 }
359 }
360
360
361 /// Calculate the hash of a revision given its data and its parents.
361 /// Calculate the hash of a revision given its data and its parents.
362 fn hash(data: &[u8], p1_hash: &[u8], p2_hash: &[u8]) -> Vec<u8> {
362 fn hash(data: &[u8], p1_hash: &[u8], p2_hash: &[u8]) -> Vec<u8> {
363 let mut hasher = Sha1::new();
363 let mut hasher = Sha1::new();
364 let (a, b) = (p1_hash, p2_hash);
364 let (a, b) = (p1_hash, p2_hash);
365 if a > b {
365 if a > b {
366 hasher.input(b);
366 hasher.input(b);
367 hasher.input(a);
367 hasher.input(a);
368 } else {
368 } else {
369 hasher.input(a);
369 hasher.input(a);
370 hasher.input(b);
370 hasher.input(b);
371 }
371 }
372 hasher.input(data);
372 hasher.input(data);
373 let mut hash = vec![0; NODE_BYTES_LENGTH];
373 let mut hash = vec![0; NODE_BYTES_LENGTH];
374 hasher.result(&mut hash);
374 hasher.result(&mut hash);
375 hash
375 hash
376 }
376 }
377
377
378 #[cfg(test)]
378 #[cfg(test)]
379 mod tests {
379 mod tests {
380 use super::*;
380 use super::*;
381
381
382 use super::super::index::IndexEntryBuilder;
382 use super::super::index::IndexEntryBuilder;
383
383
384 #[test]
384 #[test]
385 fn version_test() {
385 fn version_test() {
386 let bytes = IndexEntryBuilder::new()
386 let bytes = IndexEntryBuilder::new()
387 .is_first(true)
387 .is_first(true)
388 .with_version(1)
388 .with_version(1)
389 .build();
389 .build();
390
390
391 assert_eq!(get_version(&bytes), 1)
391 assert_eq!(get_version(&bytes), 1)
392 }
392 }
393 }
393 }
@@ -1,69 +1,84 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;
6 use std::convert::TryFrom;
7 use std::convert::TryFrom;
7
8
8 pub const HELP_TEXT: &str = "
9 pub const HELP_TEXT: &str = "
9 Output the current or given revision of files
10 Output the current or given revision of files
10 ";
11 ";
11
12
12 pub fn args() -> clap::App<'static, 'static> {
13 pub fn args() -> clap::App<'static, 'static> {
13 clap::SubCommand::with_name("cat")
14 clap::SubCommand::with_name("cat")
14 .arg(
15 .arg(
15 Arg::with_name("rev")
16 Arg::with_name("rev")
16 .help("search the repository as it is in REV")
17 .help("search the repository as it is in REV")
17 .short("-r")
18 .short("-r")
18 .long("--revision")
19 .long("--revision")
19 .value_name("REV")
20 .value_name("REV")
20 .takes_value(true),
21 .takes_value(true),
21 )
22 )
22 .arg(
23 .arg(
23 clap::Arg::with_name("files")
24 clap::Arg::with_name("files")
24 .required(true)
25 .required(true)
25 .multiple(true)
26 .multiple(true)
26 .empty_values(false)
27 .empty_values(false)
27 .value_name("FILE")
28 .value_name("FILE")
28 .help("Activity to start: activity@category"),
29 .help("Activity to start: activity@category"),
29 )
30 )
30 .about(HELP_TEXT)
31 .about(HELP_TEXT)
31 }
32 }
32
33
33 #[timed]
34 #[timed]
34 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
35 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
35 let rev = invocation.subcommand_args.value_of("rev");
36 let rev = invocation.subcommand_args.value_of("rev");
36 let file_args = match invocation.subcommand_args.values_of("files") {
37 let file_args = match invocation.subcommand_args.values_of("files") {
37 Some(files) => files.collect(),
38 Some(files) => files.collect(),
38 None => vec![],
39 None => vec![],
39 };
40 };
40
41
41 let repo = invocation.repo?;
42 let repo = invocation.repo?;
42 let cwd = hg::utils::current_dir()?;
43 let cwd = hg::utils::current_dir()?;
43 let working_directory = repo.working_directory_path();
44 let working_directory = repo.working_directory_path();
44 let working_directory = cwd.join(working_directory); // Make it absolute
45 let working_directory = cwd.join(working_directory); // Make it absolute
45
46
46 let mut files = vec![];
47 let mut files = vec![];
47 for file in file_args.iter() {
48 for file in file_args.iter() {
48 // TODO: actually normalize `..` path segments etc?
49 // TODO: actually normalize `..` path segments etc?
49 let normalized = cwd.join(&file);
50 let normalized = cwd.join(&file);
50 let stripped = normalized
51 let stripped = normalized
51 .strip_prefix(&working_directory)
52 .strip_prefix(&working_directory)
52 // TODO: error message for path arguments outside of the repo
53 // TODO: error message for path arguments outside of the repo
53 .map_err(|_| CommandError::abort(""))?;
54 .map_err(|_| CommandError::abort(""))?;
54 let hg_file = HgPathBuf::try_from(stripped.to_path_buf())
55 let hg_file = HgPathBuf::try_from(stripped.to_path_buf())
55 .map_err(|e| CommandError::abort(e.to_string()))?;
56 .map_err(|e| CommandError::abort(e.to_string()))?;
56 files.push(hg_file);
57 files.push(hg_file);
57 }
58 }
58
59
59 match rev {
60 match rev {
60 Some(rev) => {
61 Some(rev) => {
61 let data = cat(&repo, rev, &files).map_err(|e| (e, rev))?;
62 let output = cat(&repo, rev, &files).map_err(|e| (e, rev))?;
62 invocation.ui.write_stdout(&data)?;
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`",
67 )),
82 )),
68 }
83 }
69 }
84 }
@@ -1,143 +1,146 b''
1 use crate::ui::utf8_to_local;
1 use crate::ui::utf8_to_local;
2 use crate::ui::UiError;
2 use crate::ui::UiError;
3 use crate::NoRepoInCwdError;
3 use crate::NoRepoInCwdError;
4 use format_bytes::format_bytes;
4 use format_bytes::format_bytes;
5 use hg::config::{ConfigError, ConfigParseError};
5 use hg::config::{ConfigError, ConfigParseError};
6 use hg::errors::HgError;
6 use hg::errors::HgError;
7 use hg::repo::RepoError;
7 use hg::repo::RepoError;
8 use hg::revlog::revlog::RevlogError;
8 use hg::revlog::revlog::RevlogError;
9 use hg::utils::files::get_bytes_from_path;
9 use hg::utils::files::get_bytes_from_path;
10 use std::convert::From;
10 use std::convert::From;
11
11
12 /// The kind of command error
12 /// The kind of command error
13 #[derive(Debug)]
13 #[derive(Debug)]
14 pub enum CommandError {
14 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
21 /// may or may not support this feature.
24 /// may or may not support this feature.
22 UnsupportedFeature { message: Vec<u8> },
25 UnsupportedFeature { message: Vec<u8> },
23 }
26 }
24
27
25 impl CommandError {
28 impl CommandError {
26 pub fn abort(message: impl AsRef<str>) -> Self {
29 pub fn abort(message: impl AsRef<str>) -> Self {
27 CommandError::Abort {
30 CommandError::Abort {
28 // TODO: bytes-based (instead of Unicode-based) formatting
31 // TODO: bytes-based (instead of Unicode-based) formatting
29 // of error messages to handle non-UTF-8 filenames etc:
32 // of error messages to handle non-UTF-8 filenames etc:
30 // https://www.mercurial-scm.org/wiki/EncodingStrategy#Mixing_output
33 // https://www.mercurial-scm.org/wiki/EncodingStrategy#Mixing_output
31 message: utf8_to_local(message.as_ref()).into(),
34 message: utf8_to_local(message.as_ref()).into(),
32 }
35 }
33 }
36 }
34
37
35 pub fn unsupported(message: impl AsRef<str>) -> Self {
38 pub fn unsupported(message: impl AsRef<str>) -> Self {
36 CommandError::UnsupportedFeature {
39 CommandError::UnsupportedFeature {
37 message: utf8_to_local(message.as_ref()).into(),
40 message: utf8_to_local(message.as_ref()).into(),
38 }
41 }
39 }
42 }
40 }
43 }
41
44
42 /// For now we don’t differenciate between invalid CLI args and valid for `hg`
45 /// For now we don’t differenciate between invalid CLI args and valid for `hg`
43 /// but not supported yet by `rhg`.
46 /// but not supported yet by `rhg`.
44 impl From<clap::Error> for CommandError {
47 impl From<clap::Error> for CommandError {
45 fn from(error: clap::Error) -> Self {
48 fn from(error: clap::Error) -> Self {
46 CommandError::unsupported(error.to_string())
49 CommandError::unsupported(error.to_string())
47 }
50 }
48 }
51 }
49
52
50 impl From<HgError> for CommandError {
53 impl From<HgError> for CommandError {
51 fn from(error: HgError) -> Self {
54 fn from(error: HgError) -> Self {
52 match error {
55 match error {
53 HgError::UnsupportedFeature(message) => {
56 HgError::UnsupportedFeature(message) => {
54 CommandError::unsupported(message)
57 CommandError::unsupported(message)
55 }
58 }
56 _ => CommandError::abort(error.to_string()),
59 _ => CommandError::abort(error.to_string()),
57 }
60 }
58 }
61 }
59 }
62 }
60
63
61 impl From<UiError> for CommandError {
64 impl From<UiError> for CommandError {
62 fn from(_error: UiError) -> Self {
65 fn from(_error: UiError) -> Self {
63 // If we already failed writing to stdout or stderr,
66 // If we already failed writing to stdout or stderr,
64 // writing an error message to stderr about it would be likely to fail
67 // writing an error message to stderr about it would be likely to fail
65 // too.
68 // too.
66 CommandError::abort("")
69 CommandError::abort("")
67 }
70 }
68 }
71 }
69
72
70 impl From<RepoError> for CommandError {
73 impl From<RepoError> for CommandError {
71 fn from(error: RepoError) -> Self {
74 fn from(error: RepoError) -> Self {
72 match error {
75 match error {
73 RepoError::NotFound { at } => CommandError::Abort {
76 RepoError::NotFound { at } => CommandError::Abort {
74 message: format_bytes!(
77 message: format_bytes!(
75 b"abort: repository {} not found",
78 b"abort: repository {} not found",
76 get_bytes_from_path(at)
79 get_bytes_from_path(at)
77 ),
80 ),
78 },
81 },
79 RepoError::ConfigParseError(error) => error.into(),
82 RepoError::ConfigParseError(error) => error.into(),
80 RepoError::Other(error) => error.into(),
83 RepoError::Other(error) => error.into(),
81 }
84 }
82 }
85 }
83 }
86 }
84
87
85 impl<'a> From<&'a NoRepoInCwdError> for CommandError {
88 impl<'a> From<&'a NoRepoInCwdError> for CommandError {
86 fn from(error: &'a NoRepoInCwdError) -> Self {
89 fn from(error: &'a NoRepoInCwdError) -> Self {
87 let NoRepoInCwdError { cwd } = error;
90 let NoRepoInCwdError { cwd } = error;
88 CommandError::Abort {
91 CommandError::Abort {
89 message: format_bytes!(
92 message: format_bytes!(
90 b"abort: no repository found in '{}' (.hg not found)!",
93 b"abort: no repository found in '{}' (.hg not found)!",
91 get_bytes_from_path(cwd)
94 get_bytes_from_path(cwd)
92 ),
95 ),
93 }
96 }
94 }
97 }
95 }
98 }
96
99
97 impl From<ConfigError> for CommandError {
100 impl From<ConfigError> for CommandError {
98 fn from(error: ConfigError) -> Self {
101 fn from(error: ConfigError) -> Self {
99 match error {
102 match error {
100 ConfigError::Parse(error) => error.into(),
103 ConfigError::Parse(error) => error.into(),
101 ConfigError::Other(error) => error.into(),
104 ConfigError::Other(error) => error.into(),
102 }
105 }
103 }
106 }
104 }
107 }
105
108
106 impl From<ConfigParseError> for CommandError {
109 impl From<ConfigParseError> for CommandError {
107 fn from(error: ConfigParseError) -> Self {
110 fn from(error: ConfigParseError) -> Self {
108 let ConfigParseError {
111 let ConfigParseError {
109 origin,
112 origin,
110 line,
113 line,
111 message,
114 message,
112 } = error;
115 } = error;
113 let line_message = if let Some(line_number) = line {
116 let line_message = if let Some(line_number) = line {
114 format_bytes!(b":{}", line_number.to_string().into_bytes())
117 format_bytes!(b":{}", line_number.to_string().into_bytes())
115 } else {
118 } else {
116 Vec::new()
119 Vec::new()
117 };
120 };
118 CommandError::Abort {
121 CommandError::Abort {
119 message: format_bytes!(
122 message: format_bytes!(
120 b"config error at {}{}: {}",
123 b"config error at {}{}: {}",
121 origin,
124 origin,
122 line_message,
125 line_message,
123 message
126 message
124 ),
127 ),
125 }
128 }
126 }
129 }
127 }
130 }
128
131
129 impl From<(RevlogError, &str)> for CommandError {
132 impl From<(RevlogError, &str)> for CommandError {
130 fn from((err, rev): (RevlogError, &str)) -> CommandError {
133 fn from((err, rev): (RevlogError, &str)) -> CommandError {
131 match err {
134 match err {
132 RevlogError::InvalidRevision => CommandError::abort(format!(
135 RevlogError::InvalidRevision => CommandError::abort(format!(
133 "abort: invalid revision identifier: {}",
136 "abort: invalid revision identifier: {}",
134 rev
137 rev
135 )),
138 )),
136 RevlogError::AmbiguousPrefix => CommandError::abort(format!(
139 RevlogError::AmbiguousPrefix => CommandError::abort(format!(
137 "abort: ambiguous revision identifier: {}",
140 "abort: ambiguous revision identifier: {}",
138 rev
141 rev
139 )),
142 )),
140 RevlogError::Other(error) => error.into(),
143 RevlogError::Other(error) => error.into(),
141 }
144 }
142 }
145 }
143 }
146 }
@@ -1,10 +1,13 b''
1 pub type ExitCode = i32;
1 pub type ExitCode = i32;
2
2
3 /// Successful exit
3 /// Successful exit
4 pub const OK: ExitCode = 0;
4 pub const OK: ExitCode = 0;
5
5
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;
@@ -1,440 +1,442 b''
1 extern crate log;
1 extern crate log;
2 use crate::ui::Ui;
2 use crate::ui::Ui;
3 use clap::App;
3 use clap::App;
4 use clap::AppSettings;
4 use clap::AppSettings;
5 use clap::Arg;
5 use clap::Arg;
6 use clap::ArgMatches;
6 use clap::ArgMatches;
7 use format_bytes::{format_bytes, join};
7 use format_bytes::{format_bytes, join};
8 use hg::config::Config;
8 use hg::config::Config;
9 use hg::repo::{Repo, RepoError};
9 use hg::repo::{Repo, RepoError};
10 use hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes};
10 use hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes};
11 use hg::utils::SliceExt;
11 use hg::utils::SliceExt;
12 use std::ffi::OsString;
12 use std::ffi::OsString;
13 use std::path::PathBuf;
13 use std::path::PathBuf;
14 use std::process::Command;
14 use std::process::Command;
15
15
16 mod blackbox;
16 mod blackbox;
17 mod error;
17 mod error;
18 mod exitcode;
18 mod exitcode;
19 mod ui;
19 mod ui;
20 use error::CommandError;
20 use error::CommandError;
21
21
22 fn main_with_result(
22 fn main_with_result(
23 process_start_time: &blackbox::ProcessStartTime,
23 process_start_time: &blackbox::ProcessStartTime,
24 ui: &ui::Ui,
24 ui: &ui::Ui,
25 repo: Result<&Repo, &NoRepoInCwdError>,
25 repo: Result<&Repo, &NoRepoInCwdError>,
26 config: &Config,
26 config: &Config,
27 ) -> Result<(), CommandError> {
27 ) -> Result<(), CommandError> {
28 check_extensions(config)?;
28 check_extensions(config)?;
29
29
30 let app = App::new("rhg")
30 let app = App::new("rhg")
31 .global_setting(AppSettings::AllowInvalidUtf8)
31 .global_setting(AppSettings::AllowInvalidUtf8)
32 .setting(AppSettings::SubcommandRequired)
32 .setting(AppSettings::SubcommandRequired)
33 .setting(AppSettings::VersionlessSubcommands)
33 .setting(AppSettings::VersionlessSubcommands)
34 .arg(
34 .arg(
35 Arg::with_name("repository")
35 Arg::with_name("repository")
36 .help("repository root directory")
36 .help("repository root directory")
37 .short("-R")
37 .short("-R")
38 .long("--repository")
38 .long("--repository")
39 .value_name("REPO")
39 .value_name("REPO")
40 .takes_value(true)
40 .takes_value(true)
41 // Both ok: `hg -R ./foo log` or `hg log -R ./foo`
41 // Both ok: `hg -R ./foo log` or `hg log -R ./foo`
42 .global(true),
42 .global(true),
43 )
43 )
44 .arg(
44 .arg(
45 Arg::with_name("config")
45 Arg::with_name("config")
46 .help("set/override config option (use 'section.name=value')")
46 .help("set/override config option (use 'section.name=value')")
47 .long("--config")
47 .long("--config")
48 .value_name("CONFIG")
48 .value_name("CONFIG")
49 .takes_value(true)
49 .takes_value(true)
50 .global(true)
50 .global(true)
51 // Ok: `--config section.key1=val --config section.key2=val2`
51 // Ok: `--config section.key1=val --config section.key2=val2`
52 .multiple(true)
52 .multiple(true)
53 // Not ok: `--config section.key1=val section.key2=val2`
53 // Not ok: `--config section.key1=val section.key2=val2`
54 .number_of_values(1),
54 .number_of_values(1),
55 )
55 )
56 .arg(
56 .arg(
57 Arg::with_name("cwd")
57 Arg::with_name("cwd")
58 .help("change working directory")
58 .help("change working directory")
59 .long("--cwd")
59 .long("--cwd")
60 .value_name("DIR")
60 .value_name("DIR")
61 .takes_value(true)
61 .takes_value(true)
62 .global(true),
62 .global(true),
63 )
63 )
64 .version("0.0.1");
64 .version("0.0.1");
65 let app = add_subcommand_args(app);
65 let app = add_subcommand_args(app);
66
66
67 let matches = app.clone().get_matches_safe()?;
67 let matches = app.clone().get_matches_safe()?;
68
68
69 let (subcommand_name, subcommand_matches) = matches.subcommand();
69 let (subcommand_name, subcommand_matches) = matches.subcommand();
70 let run = subcommand_run_fn(subcommand_name)
70 let run = subcommand_run_fn(subcommand_name)
71 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
71 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
72 let subcommand_args = subcommand_matches
72 let subcommand_args = subcommand_matches
73 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
73 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
74
74
75 let invocation = CliInvocation {
75 let invocation = CliInvocation {
76 ui,
76 ui,
77 subcommand_args,
77 subcommand_args,
78 config,
78 config,
79 repo,
79 repo,
80 };
80 };
81 let blackbox = blackbox::Blackbox::new(&invocation, process_start_time)?;
81 let blackbox = blackbox::Blackbox::new(&invocation, process_start_time)?;
82 blackbox.log_command_start();
82 blackbox.log_command_start();
83 let result = run(&invocation);
83 let result = run(&invocation);
84 blackbox.log_command_end(exit_code(&result));
84 blackbox.log_command_end(exit_code(&result));
85 result
85 result
86 }
86 }
87
87
88 fn main() {
88 fn main() {
89 // Run this first, before we find out if the blackbox extension is even
89 // Run this first, before we find out if the blackbox extension is even
90 // enabled, in order to include everything in-between in the duration
90 // enabled, in order to include everything in-between in the duration
91 // measurements. Reading config files can be slow if they’re on NFS.
91 // measurements. Reading config files can be slow if they’re on NFS.
92 let process_start_time = blackbox::ProcessStartTime::now();
92 let process_start_time = blackbox::ProcessStartTime::now();
93
93
94 env_logger::init();
94 env_logger::init();
95 let ui = ui::Ui::new();
95 let ui = ui::Ui::new();
96
96
97 let early_args = EarlyArgs::parse(std::env::args_os());
97 let early_args = EarlyArgs::parse(std::env::args_os());
98
98
99 let initial_current_dir = early_args.cwd.map(|cwd| {
99 let initial_current_dir = early_args.cwd.map(|cwd| {
100 let cwd = get_path_from_bytes(&cwd);
100 let cwd = get_path_from_bytes(&cwd);
101 std::env::current_dir()
101 std::env::current_dir()
102 .and_then(|initial| {
102 .and_then(|initial| {
103 std::env::set_current_dir(cwd)?;
103 std::env::set_current_dir(cwd)?;
104 Ok(initial)
104 Ok(initial)
105 })
105 })
106 .unwrap_or_else(|error| {
106 .unwrap_or_else(|error| {
107 exit(
107 exit(
108 &None,
108 &None,
109 &ui,
109 &ui,
110 OnUnsupported::Abort,
110 OnUnsupported::Abort,
111 Err(CommandError::abort(format!(
111 Err(CommandError::abort(format!(
112 "abort: {}: '{}'",
112 "abort: {}: '{}'",
113 error,
113 error,
114 cwd.display()
114 cwd.display()
115 ))),
115 ))),
116 )
116 )
117 })
117 })
118 });
118 });
119
119
120 let non_repo_config =
120 let non_repo_config =
121 Config::load(early_args.config).unwrap_or_else(|error| {
121 Config::load(early_args.config).unwrap_or_else(|error| {
122 // Normally this is decided based on config, but we don’t have that
122 // Normally this is decided based on config, but we don’t have that
123 // available. As of this writing config loading never returns an
123 // available. As of this writing config loading never returns an
124 // "unsupported" error but that is not enforced by the type system.
124 // "unsupported" error but that is not enforced by the type system.
125 let on_unsupported = OnUnsupported::Abort;
125 let on_unsupported = OnUnsupported::Abort;
126
126
127 exit(&initial_current_dir, &ui, on_unsupported, Err(error.into()))
127 exit(&initial_current_dir, &ui, on_unsupported, Err(error.into()))
128 });
128 });
129
129
130 if let Some(repo_path_bytes) = &early_args.repo {
130 if let Some(repo_path_bytes) = &early_args.repo {
131 lazy_static::lazy_static! {
131 lazy_static::lazy_static! {
132 static ref SCHEME_RE: regex::bytes::Regex =
132 static ref SCHEME_RE: regex::bytes::Regex =
133 // Same as `_matchscheme` in `mercurial/util.py`
133 // Same as `_matchscheme` in `mercurial/util.py`
134 regex::bytes::Regex::new("^[a-zA-Z0-9+.\\-]+:").unwrap();
134 regex::bytes::Regex::new("^[a-zA-Z0-9+.\\-]+:").unwrap();
135 }
135 }
136 if SCHEME_RE.is_match(&repo_path_bytes) {
136 if SCHEME_RE.is_match(&repo_path_bytes) {
137 exit(
137 exit(
138 &initial_current_dir,
138 &initial_current_dir,
139 &ui,
139 &ui,
140 OnUnsupported::from_config(&non_repo_config),
140 OnUnsupported::from_config(&non_repo_config),
141 Err(CommandError::UnsupportedFeature {
141 Err(CommandError::UnsupportedFeature {
142 message: format_bytes!(
142 message: format_bytes!(
143 b"URL-like --repository {}",
143 b"URL-like --repository {}",
144 repo_path_bytes
144 repo_path_bytes
145 ),
145 ),
146 }),
146 }),
147 )
147 )
148 }
148 }
149 }
149 }
150 let repo_path = early_args.repo.as_deref().map(get_path_from_bytes);
150 let repo_path = early_args.repo.as_deref().map(get_path_from_bytes);
151 let repo_result = match Repo::find(&non_repo_config, repo_path) {
151 let repo_result = match Repo::find(&non_repo_config, repo_path) {
152 Ok(repo) => Ok(repo),
152 Ok(repo) => Ok(repo),
153 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
153 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
154 // Not finding a repo is not fatal yet, if `-R` was not given
154 // Not finding a repo is not fatal yet, if `-R` was not given
155 Err(NoRepoInCwdError { cwd: at })
155 Err(NoRepoInCwdError { cwd: at })
156 }
156 }
157 Err(error) => exit(
157 Err(error) => exit(
158 &initial_current_dir,
158 &initial_current_dir,
159 &ui,
159 &ui,
160 OnUnsupported::from_config(&non_repo_config),
160 OnUnsupported::from_config(&non_repo_config),
161 Err(error.into()),
161 Err(error.into()),
162 ),
162 ),
163 };
163 };
164
164
165 let config = if let Ok(repo) = &repo_result {
165 let config = if let Ok(repo) = &repo_result {
166 repo.config()
166 repo.config()
167 } else {
167 } else {
168 &non_repo_config
168 &non_repo_config
169 };
169 };
170
170
171 let result = main_with_result(
171 let result = main_with_result(
172 &process_start_time,
172 &process_start_time,
173 &ui,
173 &ui,
174 repo_result.as_ref(),
174 repo_result.as_ref(),
175 config,
175 config,
176 );
176 );
177 exit(
177 exit(
178 &initial_current_dir,
178 &initial_current_dir,
179 &ui,
179 &ui,
180 OnUnsupported::from_config(config),
180 OnUnsupported::from_config(config),
181 result,
181 result,
182 )
182 )
183 }
183 }
184
184
185 fn exit_code(result: &Result<(), CommandError>) -> i32 {
185 fn exit_code(result: &Result<(), CommandError>) -> i32 {
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.
192 Err(CommandError::UnsupportedFeature { .. }) => {
193 Err(CommandError::UnsupportedFeature { .. }) => {
193 exitcode::UNIMPLEMENTED
194 exitcode::UNIMPLEMENTED
194 }
195 }
195 }
196 }
196 }
197 }
197
198
198 fn exit(
199 fn exit(
199 initial_current_dir: &Option<PathBuf>,
200 initial_current_dir: &Option<PathBuf>,
200 ui: &Ui,
201 ui: &Ui,
201 mut on_unsupported: OnUnsupported,
202 mut on_unsupported: OnUnsupported,
202 result: Result<(), CommandError>,
203 result: Result<(), CommandError>,
203 ) -> ! {
204 ) -> ! {
204 if let (
205 if let (
205 OnUnsupported::Fallback { executable },
206 OnUnsupported::Fallback { executable },
206 Err(CommandError::UnsupportedFeature { .. }),
207 Err(CommandError::UnsupportedFeature { .. }),
207 ) = (&on_unsupported, &result)
208 ) = (&on_unsupported, &result)
208 {
209 {
209 let mut args = std::env::args_os();
210 let mut args = std::env::args_os();
210 let executable_path = get_path_from_bytes(&executable);
211 let executable_path = get_path_from_bytes(&executable);
211 let this_executable = args.next().expect("exepcted argv[0] to exist");
212 let this_executable = args.next().expect("exepcted argv[0] to exist");
212 if executable_path == &PathBuf::from(this_executable) {
213 if executable_path == &PathBuf::from(this_executable) {
213 // Avoid spawning infinitely many processes until resource
214 // Avoid spawning infinitely many processes until resource
214 // exhaustion.
215 // exhaustion.
215 let _ = ui.write_stderr(&format_bytes!(
216 let _ = ui.write_stderr(&format_bytes!(
216 b"Blocking recursive fallback. The 'rhg.fallback-executable = {}' config \
217 b"Blocking recursive fallback. The 'rhg.fallback-executable = {}' config \
217 points to `rhg` itself.\n",
218 points to `rhg` itself.\n",
218 executable
219 executable
219 ));
220 ));
220 on_unsupported = OnUnsupported::Abort
221 on_unsupported = OnUnsupported::Abort
221 } else {
222 } else {
222 // `args` is now `argv[1..]` since we’ve already consumed `argv[0]`
223 // `args` is now `argv[1..]` since we’ve already consumed `argv[0]`
223 let mut command = Command::new(executable_path);
224 let mut command = Command::new(executable_path);
224 command.args(args);
225 command.args(args);
225 if let Some(initial) = initial_current_dir {
226 if let Some(initial) = initial_current_dir {
226 command.current_dir(initial);
227 command.current_dir(initial);
227 }
228 }
228 let result = command.status();
229 let result = command.status();
229 match result {
230 match result {
230 Ok(status) => std::process::exit(
231 Ok(status) => std::process::exit(
231 status.code().unwrap_or(exitcode::ABORT),
232 status.code().unwrap_or(exitcode::ABORT),
232 ),
233 ),
233 Err(error) => {
234 Err(error) => {
234 let _ = ui.write_stderr(&format_bytes!(
235 let _ = ui.write_stderr(&format_bytes!(
235 b"tried to fall back to a '{}' sub-process but got error {}\n",
236 b"tried to fall back to a '{}' sub-process but got error {}\n",
236 executable, format_bytes::Utf8(error)
237 executable, format_bytes::Utf8(error)
237 ));
238 ));
238 on_unsupported = OnUnsupported::Abort
239 on_unsupported = OnUnsupported::Abort
239 }
240 }
240 }
241 }
241 }
242 }
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
248 // with failure code so there’s not much more we can do.
250 // with failure code so there’s not much more we can do.
249 let _ = ui.write_stderr(&format_bytes!(b"{}\n", message));
251 let _ = ui.write_stderr(&format_bytes!(b"{}\n", message));
250 }
252 }
251 }
253 }
252 Err(CommandError::UnsupportedFeature { message }) => {
254 Err(CommandError::UnsupportedFeature { message }) => {
253 match on_unsupported {
255 match on_unsupported {
254 OnUnsupported::Abort => {
256 OnUnsupported::Abort => {
255 let _ = ui.write_stderr(&format_bytes!(
257 let _ = ui.write_stderr(&format_bytes!(
256 b"unsupported feature: {}\n",
258 b"unsupported feature: {}\n",
257 message
259 message
258 ));
260 ));
259 }
261 }
260 OnUnsupported::AbortSilent => {}
262 OnUnsupported::AbortSilent => {}
261 OnUnsupported::Fallback { .. } => unreachable!(),
263 OnUnsupported::Fallback { .. } => unreachable!(),
262 }
264 }
263 }
265 }
264 }
266 }
265 std::process::exit(exit_code(&result))
267 std::process::exit(exit_code(&result))
266 }
268 }
267
269
268 macro_rules! subcommands {
270 macro_rules! subcommands {
269 ($( $command: ident )+) => {
271 ($( $command: ident )+) => {
270 mod commands {
272 mod commands {
271 $(
273 $(
272 pub mod $command;
274 pub mod $command;
273 )+
275 )+
274 }
276 }
275
277
276 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
278 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
277 app
279 app
278 $(
280 $(
279 .subcommand(commands::$command::args())
281 .subcommand(commands::$command::args())
280 )+
282 )+
281 }
283 }
282
284
283 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
285 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
284
286
285 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
287 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
286 match name {
288 match name {
287 $(
289 $(
288 stringify!($command) => Some(commands::$command::run),
290 stringify!($command) => Some(commands::$command::run),
289 )+
291 )+
290 _ => None,
292 _ => None,
291 }
293 }
292 }
294 }
293 };
295 };
294 }
296 }
295
297
296 subcommands! {
298 subcommands! {
297 cat
299 cat
298 debugdata
300 debugdata
299 debugrequirements
301 debugrequirements
300 files
302 files
301 root
303 root
302 config
304 config
303 }
305 }
304 pub struct CliInvocation<'a> {
306 pub struct CliInvocation<'a> {
305 ui: &'a Ui,
307 ui: &'a Ui,
306 subcommand_args: &'a ArgMatches<'a>,
308 subcommand_args: &'a ArgMatches<'a>,
307 config: &'a Config,
309 config: &'a Config,
308 /// References inside `Result` is a bit peculiar but allow
310 /// References inside `Result` is a bit peculiar but allow
309 /// `invocation.repo?` to work out with `&CliInvocation` since this
311 /// `invocation.repo?` to work out with `&CliInvocation` since this
310 /// `Result` type is `Copy`.
312 /// `Result` type is `Copy`.
311 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
313 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
312 }
314 }
313
315
314 struct NoRepoInCwdError {
316 struct NoRepoInCwdError {
315 cwd: PathBuf,
317 cwd: PathBuf,
316 }
318 }
317
319
318 /// CLI arguments to be parsed "early" in order to be able to read
320 /// CLI arguments to be parsed "early" in order to be able to read
319 /// configuration before using Clap. Ideally we would also use Clap for this,
321 /// configuration before using Clap. Ideally we would also use Clap for this,
320 /// see <https://github.com/clap-rs/clap/discussions/2366>.
322 /// see <https://github.com/clap-rs/clap/discussions/2366>.
321 ///
323 ///
322 /// These arguments are still declared when we do use Clap later, so that Clap
324 /// These arguments are still declared when we do use Clap later, so that Clap
323 /// does not return an error for their presence.
325 /// does not return an error for their presence.
324 struct EarlyArgs {
326 struct EarlyArgs {
325 /// Values of all `--config` arguments. (Possibly none)
327 /// Values of all `--config` arguments. (Possibly none)
326 config: Vec<Vec<u8>>,
328 config: Vec<Vec<u8>>,
327 /// Value of the `-R` or `--repository` argument, if any.
329 /// Value of the `-R` or `--repository` argument, if any.
328 repo: Option<Vec<u8>>,
330 repo: Option<Vec<u8>>,
329 /// Value of the `--cwd` argument, if any.
331 /// Value of the `--cwd` argument, if any.
330 cwd: Option<Vec<u8>>,
332 cwd: Option<Vec<u8>>,
331 }
333 }
332
334
333 impl EarlyArgs {
335 impl EarlyArgs {
334 fn parse(args: impl IntoIterator<Item = OsString>) -> Self {
336 fn parse(args: impl IntoIterator<Item = OsString>) -> Self {
335 let mut args = args.into_iter().map(get_bytes_from_os_str);
337 let mut args = args.into_iter().map(get_bytes_from_os_str);
336 let mut config = Vec::new();
338 let mut config = Vec::new();
337 let mut repo = None;
339 let mut repo = None;
338 let mut cwd = None;
340 let mut cwd = None;
339 // Use `while let` instead of `for` so that we can also call
341 // Use `while let` instead of `for` so that we can also call
340 // `args.next()` inside the loop.
342 // `args.next()` inside the loop.
341 while let Some(arg) = args.next() {
343 while let Some(arg) = args.next() {
342 if arg == b"--config" {
344 if arg == b"--config" {
343 if let Some(value) = args.next() {
345 if let Some(value) = args.next() {
344 config.push(value)
346 config.push(value)
345 }
347 }
346 } else if let Some(value) = arg.drop_prefix(b"--config=") {
348 } else if let Some(value) = arg.drop_prefix(b"--config=") {
347 config.push(value.to_owned())
349 config.push(value.to_owned())
348 }
350 }
349
351
350 if arg == b"--cwd" {
352 if arg == b"--cwd" {
351 if let Some(value) = args.next() {
353 if let Some(value) = args.next() {
352 cwd = Some(value)
354 cwd = Some(value)
353 }
355 }
354 } else if let Some(value) = arg.drop_prefix(b"--cwd=") {
356 } else if let Some(value) = arg.drop_prefix(b"--cwd=") {
355 cwd = Some(value.to_owned())
357 cwd = Some(value.to_owned())
356 }
358 }
357
359
358 if arg == b"--repository" || arg == b"-R" {
360 if arg == b"--repository" || arg == b"-R" {
359 if let Some(value) = args.next() {
361 if let Some(value) = args.next() {
360 repo = Some(value)
362 repo = Some(value)
361 }
363 }
362 } else if let Some(value) = arg.drop_prefix(b"--repository=") {
364 } else if let Some(value) = arg.drop_prefix(b"--repository=") {
363 repo = Some(value.to_owned())
365 repo = Some(value.to_owned())
364 } else if let Some(value) = arg.drop_prefix(b"-R") {
366 } else if let Some(value) = arg.drop_prefix(b"-R") {
365 repo = Some(value.to_owned())
367 repo = Some(value.to_owned())
366 }
368 }
367 }
369 }
368 Self { config, repo, cwd }
370 Self { config, repo, cwd }
369 }
371 }
370 }
372 }
371
373
372 /// What to do when encountering some unsupported feature.
374 /// What to do when encountering some unsupported feature.
373 ///
375 ///
374 /// See `HgError::UnsupportedFeature` and `CommandError::UnsupportedFeature`.
376 /// See `HgError::UnsupportedFeature` and `CommandError::UnsupportedFeature`.
375 enum OnUnsupported {
377 enum OnUnsupported {
376 /// Print an error message describing what feature is not supported,
378 /// Print an error message describing what feature is not supported,
377 /// and exit with code 252.
379 /// and exit with code 252.
378 Abort,
380 Abort,
379 /// Silently exit with code 252.
381 /// Silently exit with code 252.
380 AbortSilent,
382 AbortSilent,
381 /// Try running a Python implementation
383 /// Try running a Python implementation
382 Fallback { executable: Vec<u8> },
384 Fallback { executable: Vec<u8> },
383 }
385 }
384
386
385 impl OnUnsupported {
387 impl OnUnsupported {
386 const DEFAULT: Self = OnUnsupported::Abort;
388 const DEFAULT: Self = OnUnsupported::Abort;
387 const DEFAULT_FALLBACK_EXECUTABLE: &'static [u8] = b"hg";
389 const DEFAULT_FALLBACK_EXECUTABLE: &'static [u8] = b"hg";
388
390
389 fn from_config(config: &Config) -> Self {
391 fn from_config(config: &Config) -> Self {
390 match config
392 match config
391 .get(b"rhg", b"on-unsupported")
393 .get(b"rhg", b"on-unsupported")
392 .map(|value| value.to_ascii_lowercase())
394 .map(|value| value.to_ascii_lowercase())
393 .as_deref()
395 .as_deref()
394 {
396 {
395 Some(b"abort") => OnUnsupported::Abort,
397 Some(b"abort") => OnUnsupported::Abort,
396 Some(b"abort-silent") => OnUnsupported::AbortSilent,
398 Some(b"abort-silent") => OnUnsupported::AbortSilent,
397 Some(b"fallback") => OnUnsupported::Fallback {
399 Some(b"fallback") => OnUnsupported::Fallback {
398 executable: config
400 executable: config
399 .get(b"rhg", b"fallback-executable")
401 .get(b"rhg", b"fallback-executable")
400 .unwrap_or(Self::DEFAULT_FALLBACK_EXECUTABLE)
402 .unwrap_or(Self::DEFAULT_FALLBACK_EXECUTABLE)
401 .to_owned(),
403 .to_owned(),
402 },
404 },
403 None => Self::DEFAULT,
405 None => Self::DEFAULT,
404 Some(_) => {
406 Some(_) => {
405 // TODO: warn about unknown config value
407 // TODO: warn about unknown config value
406 Self::DEFAULT
408 Self::DEFAULT
407 }
409 }
408 }
410 }
409 }
411 }
410 }
412 }
411
413
412 const SUPPORTED_EXTENSIONS: &[&[u8]] = &[b"blackbox", b"share"];
414 const SUPPORTED_EXTENSIONS: &[&[u8]] = &[b"blackbox", b"share"];
413
415
414 fn check_extensions(config: &Config) -> Result<(), CommandError> {
416 fn check_extensions(config: &Config) -> Result<(), CommandError> {
415 let enabled = config.get_section_keys(b"extensions");
417 let enabled = config.get_section_keys(b"extensions");
416
418
417 let mut unsupported = enabled;
419 let mut unsupported = enabled;
418 for supported in SUPPORTED_EXTENSIONS {
420 for supported in SUPPORTED_EXTENSIONS {
419 unsupported.remove(supported);
421 unsupported.remove(supported);
420 }
422 }
421
423
422 if let Some(ignored_list) =
424 if let Some(ignored_list) =
423 config.get_simple_list(b"rhg", b"ignored-extensions")
425 config.get_simple_list(b"rhg", b"ignored-extensions")
424 {
426 {
425 for ignored in ignored_list {
427 for ignored in ignored_list {
426 unsupported.remove(ignored);
428 unsupported.remove(ignored);
427 }
429 }
428 }
430 }
429
431
430 if unsupported.is_empty() {
432 if unsupported.is_empty() {
431 Ok(())
433 Ok(())
432 } else {
434 } else {
433 Err(CommandError::UnsupportedFeature {
435 Err(CommandError::UnsupportedFeature {
434 message: format_bytes!(
436 message: format_bytes!(
435 b"extensions: {} (consider adding them to 'rhg.ignored-extensions' config)",
437 b"extensions: {} (consider adding them to 'rhg.ignored-extensions' config)",
436 join(unsupported, b", ")
438 join(unsupported, b", ")
437 ),
439 ),
438 })
440 })
439 }
441 }
440 }
442 }
General Comments 0
You need to be logged in to leave comments. Login now