Show More
@@ -1,172 +1,176 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::convert::From; |
|
8 | use std::convert::From; | |
9 | use std::path::{Path, PathBuf}; |
|
9 | use std::path::{Path, PathBuf}; | |
10 |
|
10 | |||
11 | use crate::revlog::changelog::Changelog; |
|
11 | use crate::revlog::changelog::Changelog; | |
12 | use crate::revlog::manifest::{Manifest, ManifestEntry}; |
|
12 | use crate::revlog::manifest::{Manifest, ManifestEntry}; | |
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; | |||
|
17 | use crate::revlog::NodePrefix; | |||
16 | use crate::revlog::Revision; |
|
18 | use crate::revlog::Revision; | |
17 | use crate::utils::files::get_path_from_bytes; |
|
19 | use crate::utils::files::get_path_from_bytes; | |
18 | use crate::utils::hg_path::{HgPath, HgPathBuf}; |
|
20 | use crate::utils::hg_path::{HgPath, HgPathBuf}; | |
19 |
|
21 | |||
20 | const METADATA_DELIMITER: [u8; 2] = [b'\x01', b'\n']; |
|
22 | const METADATA_DELIMITER: [u8; 2] = [b'\x01', b'\n']; | |
21 |
|
23 | |||
22 | /// Kind of error encountered by `CatRev` |
|
24 | /// Kind of error encountered by `CatRev` | |
23 | #[derive(Debug)] |
|
25 | #[derive(Debug)] | |
24 | pub enum CatRevErrorKind { |
|
26 | pub enum CatRevErrorKind { | |
25 | /// Error when reading a `revlog` file. |
|
27 | /// Error when reading a `revlog` file. | |
26 | IoError(std::io::Error), |
|
28 | IoError(std::io::Error), | |
27 | /// The revision has not been found. |
|
29 | /// The revision has not been found. | |
28 | InvalidRevision, |
|
30 | InvalidRevision, | |
29 | /// Found more than one revision whose ID match the requested prefix |
|
31 | /// Found more than one revision whose ID match the requested prefix | |
30 | AmbiguousPrefix, |
|
32 | AmbiguousPrefix, | |
31 | /// A `revlog` file is corrupted. |
|
33 | /// A `revlog` file is corrupted. | |
32 | CorruptedRevlog, |
|
34 | CorruptedRevlog, | |
33 | /// The `revlog` format version is not supported. |
|
35 | /// The `revlog` format version is not supported. | |
34 | UnsuportedRevlogVersion(u16), |
|
36 | UnsuportedRevlogVersion(u16), | |
35 | /// The `revlog` data format is not supported. |
|
37 | /// The `revlog` data format is not supported. | |
36 | UnknowRevlogDataFormat(u8), |
|
38 | UnknowRevlogDataFormat(u8), | |
37 | } |
|
39 | } | |
38 |
|
40 | |||
39 | /// A `CatRev` error |
|
41 | /// A `CatRev` error | |
40 | #[derive(Debug)] |
|
42 | #[derive(Debug)] | |
41 | pub struct CatRevError { |
|
43 | pub struct CatRevError { | |
42 | /// Kind of error encountered by `CatRev` |
|
44 | /// Kind of error encountered by `CatRev` | |
43 | pub kind: CatRevErrorKind, |
|
45 | pub kind: CatRevErrorKind, | |
44 | } |
|
46 | } | |
45 |
|
47 | |||
46 | impl From<CatRevErrorKind> for CatRevError { |
|
48 | impl From<CatRevErrorKind> for CatRevError { | |
47 | fn from(kind: CatRevErrorKind) -> Self { |
|
49 | fn from(kind: CatRevErrorKind) -> Self { | |
48 | CatRevError { kind } |
|
50 | CatRevError { kind } | |
49 | } |
|
51 | } | |
50 | } |
|
52 | } | |
51 |
|
53 | |||
52 | impl From<RevlogError> for CatRevError { |
|
54 | impl From<RevlogError> for CatRevError { | |
53 | fn from(err: RevlogError) -> Self { |
|
55 | fn from(err: RevlogError) -> Self { | |
54 | match err { |
|
56 | match err { | |
55 | RevlogError::IoError(err) => CatRevErrorKind::IoError(err), |
|
57 | RevlogError::IoError(err) => CatRevErrorKind::IoError(err), | |
56 | RevlogError::UnsuportedVersion(version) => { |
|
58 | RevlogError::UnsuportedVersion(version) => { | |
57 | CatRevErrorKind::UnsuportedRevlogVersion(version) |
|
59 | CatRevErrorKind::UnsuportedRevlogVersion(version) | |
58 | } |
|
60 | } | |
59 | RevlogError::InvalidRevision => CatRevErrorKind::InvalidRevision, |
|
61 | RevlogError::InvalidRevision => CatRevErrorKind::InvalidRevision, | |
60 | RevlogError::AmbiguousPrefix => CatRevErrorKind::AmbiguousPrefix, |
|
62 | RevlogError::AmbiguousPrefix => CatRevErrorKind::AmbiguousPrefix, | |
61 | RevlogError::Corrupted => CatRevErrorKind::CorruptedRevlog, |
|
63 | RevlogError::Corrupted => CatRevErrorKind::CorruptedRevlog, | |
62 | RevlogError::UnknowDataFormat(format) => { |
|
64 | RevlogError::UnknowDataFormat(format) => { | |
63 | CatRevErrorKind::UnknowRevlogDataFormat(format) |
|
65 | CatRevErrorKind::UnknowRevlogDataFormat(format) | |
64 | } |
|
66 | } | |
65 | } |
|
67 | } | |
66 | .into() |
|
68 | .into() | |
67 | } |
|
69 | } | |
68 | } |
|
70 | } | |
69 |
|
71 | |||
70 | /// List files under Mercurial control at a given revision. |
|
72 | /// List files under Mercurial control at a given revision. | |
71 | pub struct CatRev<'a> { |
|
73 | pub struct CatRev<'a> { | |
72 | root: &'a PathBuf, |
|
74 | root: &'a PathBuf, | |
73 | /// The revision to cat the files from. |
|
75 | /// The revision to cat the files from. | |
74 | rev: &'a str, |
|
76 | rev: &'a str, | |
75 | /// The files to output. |
|
77 | /// The files to output. | |
76 | files: &'a [HgPathBuf], |
|
78 | files: &'a [HgPathBuf], | |
77 | /// The changelog file |
|
79 | /// The changelog file | |
78 | changelog: Changelog, |
|
80 | changelog: Changelog, | |
79 | /// The manifest file |
|
81 | /// The manifest file | |
80 | manifest: Manifest, |
|
82 | manifest: Manifest, | |
81 | /// The manifest entry corresponding to the revision. |
|
83 | /// The manifest entry corresponding to the revision. | |
82 | /// |
|
84 | /// | |
83 | /// Used to hold the owner of the returned references. |
|
85 | /// Used to hold the owner of the returned references. | |
84 | manifest_entry: Option<ManifestEntry>, |
|
86 | manifest_entry: Option<ManifestEntry>, | |
85 | } |
|
87 | } | |
86 |
|
88 | |||
87 | impl<'a> CatRev<'a> { |
|
89 | impl<'a> CatRev<'a> { | |
88 | pub fn new( |
|
90 | pub fn new( | |
89 | root: &'a PathBuf, |
|
91 | root: &'a PathBuf, | |
90 | rev: &'a str, |
|
92 | rev: &'a str, | |
91 | files: &'a [HgPathBuf], |
|
93 | files: &'a [HgPathBuf], | |
92 | ) -> Result<Self, CatRevError> { |
|
94 | ) -> Result<Self, CatRevError> { | |
93 | let changelog = Changelog::open(&root)?; |
|
95 | let changelog = Changelog::open(&root)?; | |
94 | let manifest = Manifest::open(&root)?; |
|
96 | let manifest = Manifest::open(&root)?; | |
95 | let manifest_entry = None; |
|
97 | let manifest_entry = None; | |
96 |
|
98 | |||
97 | Ok(Self { |
|
99 | Ok(Self { | |
98 | root, |
|
100 | root, | |
99 | rev, |
|
101 | rev, | |
100 | files, |
|
102 | files, | |
101 | changelog, |
|
103 | changelog, | |
102 | manifest, |
|
104 | manifest, | |
103 | manifest_entry, |
|
105 | manifest_entry, | |
104 | }) |
|
106 | }) | |
105 | } |
|
107 | } | |
106 |
|
108 | |||
107 | pub fn run(&mut self) -> Result<Vec<u8>, CatRevError> { |
|
109 | pub fn run(&mut self) -> Result<Vec<u8>, CatRevError> { | |
108 | let changelog_entry = match self.rev.parse::<Revision>() { |
|
110 | let changelog_entry = match self.rev.parse::<Revision>() { | |
109 | Ok(rev) => self.changelog.get_rev(rev)?, |
|
111 | Ok(rev) => self.changelog.get_rev(rev)?, | |
110 | _ => { |
|
112 | _ => { | |
111 |
let changelog_node = |
|
113 | let changelog_node = NodePrefix::from_hex(&self.rev) | |
112 | .map_err(|_| CatRevErrorKind::InvalidRevision)?; |
|
114 | .map_err(|_| CatRevErrorKind::InvalidRevision)?; | |
113 |
self.changelog.get_node( |
|
115 | self.changelog.get_node(changelog_node.borrow())? | |
114 | } |
|
116 | } | |
115 | }; |
|
117 | }; | |
116 |
let manifest_node = |
|
118 | let manifest_node = Node::from_hex(&changelog_entry.manifest_node()?) | |
117 | .map_err(|_| CatRevErrorKind::CorruptedRevlog)?; |
|
119 | .map_err(|_| CatRevErrorKind::CorruptedRevlog)?; | |
118 |
|
120 | |||
119 | self.manifest_entry = Some(self.manifest.get_node(&manifest_node)?); |
|
121 | self.manifest_entry = | |
|
122 | Some(self.manifest.get_node((&manifest_node).into())?); | |||
120 | if let Some(ref manifest_entry) = self.manifest_entry { |
|
123 | if let Some(ref manifest_entry) = self.manifest_entry { | |
121 | let mut bytes = vec![]; |
|
124 | let mut bytes = vec![]; | |
122 |
|
125 | |||
123 | for (manifest_file, node_bytes) in |
|
126 | for (manifest_file, node_bytes) in | |
124 | manifest_entry.files_with_nodes() |
|
127 | manifest_entry.files_with_nodes() | |
125 | { |
|
128 | { | |
126 | for cat_file in self.files.iter() { |
|
129 | for cat_file in self.files.iter() { | |
127 | if cat_file.as_bytes() == manifest_file.as_bytes() { |
|
130 | if cat_file.as_bytes() == manifest_file.as_bytes() { | |
128 | let index_path = |
|
131 | let index_path = | |
129 | store_path(self.root, manifest_file, b".i"); |
|
132 | store_path(self.root, manifest_file, b".i"); | |
130 | let data_path = |
|
133 | let data_path = | |
131 | store_path(self.root, manifest_file, b".d"); |
|
134 | store_path(self.root, manifest_file, b".d"); | |
132 |
|
135 | |||
133 | let file_log = |
|
136 | let file_log = | |
134 | Revlog::open(&index_path, Some(&data_path))?; |
|
137 | Revlog::open(&index_path, Some(&data_path))?; | |
135 |
let file_node = |
|
138 | let file_node = Node::from_hex(node_bytes) | |
136 | .map_err(|_| CatRevErrorKind::CorruptedRevlog)?; |
|
139 | .map_err(|_| CatRevErrorKind::CorruptedRevlog)?; | |
137 |
let file_rev = |
|
140 | let file_rev = | |
|
141 | file_log.get_node_rev((&file_node).into())?; | |||
138 | let data = file_log.get_rev_data(file_rev)?; |
|
142 | let data = file_log.get_rev_data(file_rev)?; | |
139 | if data.starts_with(&METADATA_DELIMITER) { |
|
143 | if data.starts_with(&METADATA_DELIMITER) { | |
140 | let end_delimiter_position = data |
|
144 | let end_delimiter_position = data | |
141 | [METADATA_DELIMITER.len()..] |
|
145 | [METADATA_DELIMITER.len()..] | |
142 | .windows(METADATA_DELIMITER.len()) |
|
146 | .windows(METADATA_DELIMITER.len()) | |
143 | .position(|bytes| bytes == METADATA_DELIMITER); |
|
147 | .position(|bytes| bytes == METADATA_DELIMITER); | |
144 | if let Some(position) = end_delimiter_position { |
|
148 | if let Some(position) = end_delimiter_position { | |
145 | let offset = METADATA_DELIMITER.len() * 2; |
|
149 | let offset = METADATA_DELIMITER.len() * 2; | |
146 | bytes.extend(data[position + offset..].iter()); |
|
150 | bytes.extend(data[position + offset..].iter()); | |
147 | } |
|
151 | } | |
148 | } else { |
|
152 | } else { | |
149 | bytes.extend(data); |
|
153 | bytes.extend(data); | |
150 | } |
|
154 | } | |
151 | } |
|
155 | } | |
152 | } |
|
156 | } | |
153 | } |
|
157 | } | |
154 |
|
158 | |||
155 | Ok(bytes) |
|
159 | Ok(bytes) | |
156 | } else { |
|
160 | } else { | |
157 | unreachable!("manifest_entry should have been stored"); |
|
161 | unreachable!("manifest_entry should have been stored"); | |
158 | } |
|
162 | } | |
159 | } |
|
163 | } | |
160 | } |
|
164 | } | |
161 |
|
165 | |||
162 | fn store_path(root: &Path, hg_path: &HgPath, suffix: &[u8]) -> PathBuf { |
|
166 | fn store_path(root: &Path, hg_path: &HgPath, suffix: &[u8]) -> PathBuf { | |
163 | let encoded_bytes = |
|
167 | let encoded_bytes = | |
164 | path_encode(&[b"data/", hg_path.as_bytes(), suffix].concat()); |
|
168 | path_encode(&[b"data/", hg_path.as_bytes(), suffix].concat()); | |
165 | [ |
|
169 | [ | |
166 | root, |
|
170 | root, | |
167 | &Path::new(".hg/store/"), |
|
171 | &Path::new(".hg/store/"), | |
168 | get_path_from_bytes(&encoded_bytes), |
|
172 | get_path_from_bytes(&encoded_bytes), | |
169 | ] |
|
173 | ] | |
170 | .iter() |
|
174 | .iter() | |
171 | .collect() |
|
175 | .collect() | |
172 | } |
|
176 | } |
@@ -1,119 +1,120 b'' | |||||
1 | // debugdata.rs |
|
1 | // debugdata.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 super::find_root; |
|
8 | use super::find_root; | |
9 | use crate::revlog::revlog::{Revlog, RevlogError}; |
|
9 | use crate::revlog::revlog::{Revlog, RevlogError}; | |
|
10 | use crate::revlog::NodePrefix; | |||
10 | use crate::revlog::Revision; |
|
11 | use crate::revlog::Revision; | |
11 |
|
12 | |||
12 | /// Kind of data to debug |
|
13 | /// Kind of data to debug | |
13 | #[derive(Debug, Copy, Clone)] |
|
14 | #[derive(Debug, Copy, Clone)] | |
14 | pub enum DebugDataKind { |
|
15 | pub enum DebugDataKind { | |
15 | Changelog, |
|
16 | Changelog, | |
16 | Manifest, |
|
17 | Manifest, | |
17 | } |
|
18 | } | |
18 |
|
19 | |||
19 | /// Kind of error encountered by DebugData |
|
20 | /// Kind of error encountered by DebugData | |
20 | #[derive(Debug)] |
|
21 | #[derive(Debug)] | |
21 | pub enum DebugDataErrorKind { |
|
22 | pub enum DebugDataErrorKind { | |
22 | FindRootError(find_root::FindRootError), |
|
23 | FindRootError(find_root::FindRootError), | |
23 | /// Error when reading a `revlog` file. |
|
24 | /// Error when reading a `revlog` file. | |
24 | IoError(std::io::Error), |
|
25 | IoError(std::io::Error), | |
25 | /// The revision has not been found. |
|
26 | /// The revision has not been found. | |
26 | InvalidRevision, |
|
27 | InvalidRevision, | |
27 | /// Found more than one revision whose ID match the requested prefix |
|
28 | /// Found more than one revision whose ID match the requested prefix | |
28 | AmbiguousPrefix, |
|
29 | AmbiguousPrefix, | |
29 | /// A `revlog` file is corrupted. |
|
30 | /// A `revlog` file is corrupted. | |
30 | CorruptedRevlog, |
|
31 | CorruptedRevlog, | |
31 | /// The `revlog` format version is not supported. |
|
32 | /// The `revlog` format version is not supported. | |
32 | UnsuportedRevlogVersion(u16), |
|
33 | UnsuportedRevlogVersion(u16), | |
33 | /// The `revlog` data format is not supported. |
|
34 | /// The `revlog` data format is not supported. | |
34 | UnknowRevlogDataFormat(u8), |
|
35 | UnknowRevlogDataFormat(u8), | |
35 | } |
|
36 | } | |
36 |
|
37 | |||
37 | /// A DebugData error |
|
38 | /// A DebugData error | |
38 | #[derive(Debug)] |
|
39 | #[derive(Debug)] | |
39 | pub struct DebugDataError { |
|
40 | pub struct DebugDataError { | |
40 | /// Kind of error encountered by DebugData |
|
41 | /// Kind of error encountered by DebugData | |
41 | pub kind: DebugDataErrorKind, |
|
42 | pub kind: DebugDataErrorKind, | |
42 | } |
|
43 | } | |
43 |
|
44 | |||
44 | impl From<DebugDataErrorKind> for DebugDataError { |
|
45 | impl From<DebugDataErrorKind> for DebugDataError { | |
45 | fn from(kind: DebugDataErrorKind) -> Self { |
|
46 | fn from(kind: DebugDataErrorKind) -> Self { | |
46 | DebugDataError { kind } |
|
47 | DebugDataError { kind } | |
47 | } |
|
48 | } | |
48 | } |
|
49 | } | |
49 |
|
50 | |||
50 | impl From<find_root::FindRootError> for DebugDataError { |
|
51 | impl From<find_root::FindRootError> for DebugDataError { | |
51 | fn from(err: find_root::FindRootError) -> Self { |
|
52 | fn from(err: find_root::FindRootError) -> Self { | |
52 | let kind = DebugDataErrorKind::FindRootError(err); |
|
53 | let kind = DebugDataErrorKind::FindRootError(err); | |
53 | DebugDataError { kind } |
|
54 | DebugDataError { kind } | |
54 | } |
|
55 | } | |
55 | } |
|
56 | } | |
56 |
|
57 | |||
57 | impl From<std::io::Error> for DebugDataError { |
|
58 | impl From<std::io::Error> for DebugDataError { | |
58 | fn from(err: std::io::Error) -> Self { |
|
59 | fn from(err: std::io::Error) -> Self { | |
59 | let kind = DebugDataErrorKind::IoError(err); |
|
60 | let kind = DebugDataErrorKind::IoError(err); | |
60 | DebugDataError { kind } |
|
61 | DebugDataError { kind } | |
61 | } |
|
62 | } | |
62 | } |
|
63 | } | |
63 |
|
64 | |||
64 | impl From<RevlogError> for DebugDataError { |
|
65 | impl From<RevlogError> for DebugDataError { | |
65 | fn from(err: RevlogError) -> Self { |
|
66 | fn from(err: RevlogError) -> Self { | |
66 | match err { |
|
67 | match err { | |
67 | RevlogError::IoError(err) => DebugDataErrorKind::IoError(err), |
|
68 | RevlogError::IoError(err) => DebugDataErrorKind::IoError(err), | |
68 | RevlogError::UnsuportedVersion(version) => { |
|
69 | RevlogError::UnsuportedVersion(version) => { | |
69 | DebugDataErrorKind::UnsuportedRevlogVersion(version) |
|
70 | DebugDataErrorKind::UnsuportedRevlogVersion(version) | |
70 | } |
|
71 | } | |
71 | RevlogError::InvalidRevision => { |
|
72 | RevlogError::InvalidRevision => { | |
72 | DebugDataErrorKind::InvalidRevision |
|
73 | DebugDataErrorKind::InvalidRevision | |
73 | } |
|
74 | } | |
74 | RevlogError::AmbiguousPrefix => { |
|
75 | RevlogError::AmbiguousPrefix => { | |
75 | DebugDataErrorKind::AmbiguousPrefix |
|
76 | DebugDataErrorKind::AmbiguousPrefix | |
76 | } |
|
77 | } | |
77 | RevlogError::Corrupted => DebugDataErrorKind::CorruptedRevlog, |
|
78 | RevlogError::Corrupted => DebugDataErrorKind::CorruptedRevlog, | |
78 | RevlogError::UnknowDataFormat(format) => { |
|
79 | RevlogError::UnknowDataFormat(format) => { | |
79 | DebugDataErrorKind::UnknowRevlogDataFormat(format) |
|
80 | DebugDataErrorKind::UnknowRevlogDataFormat(format) | |
80 | } |
|
81 | } | |
81 | } |
|
82 | } | |
82 | .into() |
|
83 | .into() | |
83 | } |
|
84 | } | |
84 | } |
|
85 | } | |
85 |
|
86 | |||
86 | /// Dump the contents data of a revision. |
|
87 | /// Dump the contents data of a revision. | |
87 | pub struct DebugData<'a> { |
|
88 | pub struct DebugData<'a> { | |
88 | /// Revision or hash of the revision. |
|
89 | /// Revision or hash of the revision. | |
89 | rev: &'a str, |
|
90 | rev: &'a str, | |
90 | /// Kind of data to debug. |
|
91 | /// Kind of data to debug. | |
91 | kind: DebugDataKind, |
|
92 | kind: DebugDataKind, | |
92 | } |
|
93 | } | |
93 |
|
94 | |||
94 | impl<'a> DebugData<'a> { |
|
95 | impl<'a> DebugData<'a> { | |
95 | pub fn new(rev: &'a str, kind: DebugDataKind) -> Self { |
|
96 | pub fn new(rev: &'a str, kind: DebugDataKind) -> Self { | |
96 | DebugData { rev, kind } |
|
97 | DebugData { rev, kind } | |
97 | } |
|
98 | } | |
98 |
|
99 | |||
99 | pub fn run(&mut self) -> Result<Vec<u8>, DebugDataError> { |
|
100 | pub fn run(&mut self) -> Result<Vec<u8>, DebugDataError> { | |
100 | let root = find_root::FindRoot::new().run()?; |
|
101 | let root = find_root::FindRoot::new().run()?; | |
101 | let index_file = match self.kind { |
|
102 | let index_file = match self.kind { | |
102 | DebugDataKind::Changelog => root.join(".hg/store/00changelog.i"), |
|
103 | DebugDataKind::Changelog => root.join(".hg/store/00changelog.i"), | |
103 | DebugDataKind::Manifest => root.join(".hg/store/00manifest.i"), |
|
104 | DebugDataKind::Manifest => root.join(".hg/store/00manifest.i"), | |
104 | }; |
|
105 | }; | |
105 | let revlog = Revlog::open(&index_file, None)?; |
|
106 | let revlog = Revlog::open(&index_file, None)?; | |
106 |
|
107 | |||
107 | let data = match self.rev.parse::<Revision>() { |
|
108 | let data = match self.rev.parse::<Revision>() { | |
108 | Ok(rev) => revlog.get_rev_data(rev)?, |
|
109 | Ok(rev) => revlog.get_rev_data(rev)?, | |
109 | _ => { |
|
110 | _ => { | |
110 |
let node = |
|
111 | let node = NodePrefix::from_hex(&self.rev) | |
111 | .map_err(|_| DebugDataErrorKind::InvalidRevision)?; |
|
112 | .map_err(|_| DebugDataErrorKind::InvalidRevision)?; | |
112 |
let rev = revlog.get_node_rev( |
|
113 | let rev = revlog.get_node_rev(node.borrow())?; | |
113 | revlog.get_rev_data(rev)? |
|
114 | revlog.get_rev_data(rev)? | |
114 | } |
|
115 | } | |
115 | }; |
|
116 | }; | |
116 |
|
117 | |||
117 | Ok(data) |
|
118 | Ok(data) | |
118 | } |
|
119 | } | |
119 | } |
|
120 | } |
@@ -1,192 +1,194 b'' | |||||
1 | // list_tracked_files.rs |
|
1 | // list_tracked_files.rs | |
2 | // |
|
2 | // | |
3 | // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net> |
|
3 | // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net> | |
4 | // |
|
4 | // | |
5 | // This software may be used and distributed according to the terms of the |
|
5 | // This software may be used and distributed according to the terms of the | |
6 | // GNU General Public License version 2 or any later version. |
|
6 | // GNU General Public License version 2 or any later version. | |
7 |
|
7 | |||
8 | use crate::dirstate::parsers::parse_dirstate; |
|
8 | use crate::dirstate::parsers::parse_dirstate; | |
9 | use crate::revlog::changelog::Changelog; |
|
9 | use crate::revlog::changelog::Changelog; | |
10 | use crate::revlog::manifest::{Manifest, ManifestEntry}; |
|
10 | use crate::revlog::manifest::{Manifest, ManifestEntry}; | |
|
11 | use crate::revlog::node::{Node, NodePrefix}; | |||
11 | use crate::revlog::revlog::RevlogError; |
|
12 | use crate::revlog::revlog::RevlogError; | |
12 | use crate::revlog::Revision; |
|
13 | use crate::revlog::Revision; | |
13 | use crate::utils::hg_path::HgPath; |
|
14 | use crate::utils::hg_path::HgPath; | |
14 | use crate::{DirstateParseError, EntryState}; |
|
15 | use crate::{DirstateParseError, EntryState}; | |
15 | use rayon::prelude::*; |
|
16 | use rayon::prelude::*; | |
16 | use std::convert::From; |
|
17 | use std::convert::From; | |
17 | use std::fs; |
|
18 | use std::fs; | |
18 | use std::path::PathBuf; |
|
19 | use std::path::PathBuf; | |
19 |
|
20 | |||
20 | /// Kind of error encountered by `ListDirstateTrackedFiles` |
|
21 | /// Kind of error encountered by `ListDirstateTrackedFiles` | |
21 | #[derive(Debug)] |
|
22 | #[derive(Debug)] | |
22 | pub enum ListDirstateTrackedFilesErrorKind { |
|
23 | pub enum ListDirstateTrackedFilesErrorKind { | |
23 | /// Error when reading the `dirstate` file |
|
24 | /// Error when reading the `dirstate` file | |
24 | IoError(std::io::Error), |
|
25 | IoError(std::io::Error), | |
25 | /// Error when parsing the `dirstate` file |
|
26 | /// Error when parsing the `dirstate` file | |
26 | ParseError(DirstateParseError), |
|
27 | ParseError(DirstateParseError), | |
27 | } |
|
28 | } | |
28 |
|
29 | |||
29 | /// A `ListDirstateTrackedFiles` error |
|
30 | /// A `ListDirstateTrackedFiles` error | |
30 | #[derive(Debug)] |
|
31 | #[derive(Debug)] | |
31 | pub struct ListDirstateTrackedFilesError { |
|
32 | pub struct ListDirstateTrackedFilesError { | |
32 | /// Kind of error encountered by `ListDirstateTrackedFiles` |
|
33 | /// Kind of error encountered by `ListDirstateTrackedFiles` | |
33 | pub kind: ListDirstateTrackedFilesErrorKind, |
|
34 | pub kind: ListDirstateTrackedFilesErrorKind, | |
34 | } |
|
35 | } | |
35 |
|
36 | |||
36 | impl From<ListDirstateTrackedFilesErrorKind> |
|
37 | impl From<ListDirstateTrackedFilesErrorKind> | |
37 | for ListDirstateTrackedFilesError |
|
38 | for ListDirstateTrackedFilesError | |
38 | { |
|
39 | { | |
39 | fn from(kind: ListDirstateTrackedFilesErrorKind) -> Self { |
|
40 | fn from(kind: ListDirstateTrackedFilesErrorKind) -> Self { | |
40 | ListDirstateTrackedFilesError { kind } |
|
41 | ListDirstateTrackedFilesError { kind } | |
41 | } |
|
42 | } | |
42 | } |
|
43 | } | |
43 |
|
44 | |||
44 | impl From<std::io::Error> for ListDirstateTrackedFilesError { |
|
45 | impl From<std::io::Error> for ListDirstateTrackedFilesError { | |
45 | fn from(err: std::io::Error) -> Self { |
|
46 | fn from(err: std::io::Error) -> Self { | |
46 | let kind = ListDirstateTrackedFilesErrorKind::IoError(err); |
|
47 | let kind = ListDirstateTrackedFilesErrorKind::IoError(err); | |
47 | ListDirstateTrackedFilesError { kind } |
|
48 | ListDirstateTrackedFilesError { kind } | |
48 | } |
|
49 | } | |
49 | } |
|
50 | } | |
50 |
|
51 | |||
51 | /// List files under Mercurial control in the working directory |
|
52 | /// List files under Mercurial control in the working directory | |
52 | /// by reading the dirstate |
|
53 | /// by reading the dirstate | |
53 | pub struct ListDirstateTrackedFiles { |
|
54 | pub struct ListDirstateTrackedFiles { | |
54 | /// The `dirstate` content. |
|
55 | /// The `dirstate` content. | |
55 | content: Vec<u8>, |
|
56 | content: Vec<u8>, | |
56 | } |
|
57 | } | |
57 |
|
58 | |||
58 | impl ListDirstateTrackedFiles { |
|
59 | impl ListDirstateTrackedFiles { | |
59 | pub fn new(root: &PathBuf) -> Result<Self, ListDirstateTrackedFilesError> { |
|
60 | pub fn new(root: &PathBuf) -> Result<Self, ListDirstateTrackedFilesError> { | |
60 | let dirstate = root.join(".hg/dirstate"); |
|
61 | let dirstate = root.join(".hg/dirstate"); | |
61 | let content = fs::read(&dirstate)?; |
|
62 | let content = fs::read(&dirstate)?; | |
62 | Ok(Self { content }) |
|
63 | Ok(Self { content }) | |
63 | } |
|
64 | } | |
64 |
|
65 | |||
65 | pub fn run( |
|
66 | pub fn run( | |
66 | &mut self, |
|
67 | &mut self, | |
67 | ) -> Result<Vec<&HgPath>, ListDirstateTrackedFilesError> { |
|
68 | ) -> Result<Vec<&HgPath>, ListDirstateTrackedFilesError> { | |
68 | let (_, entries, _) = parse_dirstate(&self.content) |
|
69 | let (_, entries, _) = parse_dirstate(&self.content) | |
69 | .map_err(ListDirstateTrackedFilesErrorKind::ParseError)?; |
|
70 | .map_err(ListDirstateTrackedFilesErrorKind::ParseError)?; | |
70 | let mut files: Vec<&HgPath> = entries |
|
71 | let mut files: Vec<&HgPath> = entries | |
71 | .into_iter() |
|
72 | .into_iter() | |
72 | .filter_map(|(path, entry)| match entry.state { |
|
73 | .filter_map(|(path, entry)| match entry.state { | |
73 | EntryState::Removed => None, |
|
74 | EntryState::Removed => None, | |
74 | _ => Some(path), |
|
75 | _ => Some(path), | |
75 | }) |
|
76 | }) | |
76 | .collect(); |
|
77 | .collect(); | |
77 | files.par_sort_unstable(); |
|
78 | files.par_sort_unstable(); | |
78 | Ok(files) |
|
79 | Ok(files) | |
79 | } |
|
80 | } | |
80 | } |
|
81 | } | |
81 |
|
82 | |||
82 | /// Kind of error encountered by `ListRevTrackedFiles` |
|
83 | /// Kind of error encountered by `ListRevTrackedFiles` | |
83 | #[derive(Debug)] |
|
84 | #[derive(Debug)] | |
84 | pub enum ListRevTrackedFilesErrorKind { |
|
85 | pub enum ListRevTrackedFilesErrorKind { | |
85 | /// Error when reading a `revlog` file. |
|
86 | /// Error when reading a `revlog` file. | |
86 | IoError(std::io::Error), |
|
87 | IoError(std::io::Error), | |
87 | /// The revision has not been found. |
|
88 | /// The revision has not been found. | |
88 | InvalidRevision, |
|
89 | InvalidRevision, | |
89 | /// Found more than one revision whose ID match the requested prefix |
|
90 | /// Found more than one revision whose ID match the requested prefix | |
90 | AmbiguousPrefix, |
|
91 | AmbiguousPrefix, | |
91 | /// A `revlog` file is corrupted. |
|
92 | /// A `revlog` file is corrupted. | |
92 | CorruptedRevlog, |
|
93 | CorruptedRevlog, | |
93 | /// The `revlog` format version is not supported. |
|
94 | /// The `revlog` format version is not supported. | |
94 | UnsuportedRevlogVersion(u16), |
|
95 | UnsuportedRevlogVersion(u16), | |
95 | /// The `revlog` data format is not supported. |
|
96 | /// The `revlog` data format is not supported. | |
96 | UnknowRevlogDataFormat(u8), |
|
97 | UnknowRevlogDataFormat(u8), | |
97 | } |
|
98 | } | |
98 |
|
99 | |||
99 | /// A `ListRevTrackedFiles` error |
|
100 | /// A `ListRevTrackedFiles` error | |
100 | #[derive(Debug)] |
|
101 | #[derive(Debug)] | |
101 | pub struct ListRevTrackedFilesError { |
|
102 | pub struct ListRevTrackedFilesError { | |
102 | /// Kind of error encountered by `ListRevTrackedFiles` |
|
103 | /// Kind of error encountered by `ListRevTrackedFiles` | |
103 | pub kind: ListRevTrackedFilesErrorKind, |
|
104 | pub kind: ListRevTrackedFilesErrorKind, | |
104 | } |
|
105 | } | |
105 |
|
106 | |||
106 | impl From<ListRevTrackedFilesErrorKind> for ListRevTrackedFilesError { |
|
107 | impl From<ListRevTrackedFilesErrorKind> for ListRevTrackedFilesError { | |
107 | fn from(kind: ListRevTrackedFilesErrorKind) -> Self { |
|
108 | fn from(kind: ListRevTrackedFilesErrorKind) -> Self { | |
108 | ListRevTrackedFilesError { kind } |
|
109 | ListRevTrackedFilesError { kind } | |
109 | } |
|
110 | } | |
110 | } |
|
111 | } | |
111 |
|
112 | |||
112 | impl From<RevlogError> for ListRevTrackedFilesError { |
|
113 | impl From<RevlogError> for ListRevTrackedFilesError { | |
113 | fn from(err: RevlogError) -> Self { |
|
114 | fn from(err: RevlogError) -> Self { | |
114 | match err { |
|
115 | match err { | |
115 | RevlogError::IoError(err) => { |
|
116 | RevlogError::IoError(err) => { | |
116 | ListRevTrackedFilesErrorKind::IoError(err) |
|
117 | ListRevTrackedFilesErrorKind::IoError(err) | |
117 | } |
|
118 | } | |
118 | RevlogError::UnsuportedVersion(version) => { |
|
119 | RevlogError::UnsuportedVersion(version) => { | |
119 | ListRevTrackedFilesErrorKind::UnsuportedRevlogVersion(version) |
|
120 | ListRevTrackedFilesErrorKind::UnsuportedRevlogVersion(version) | |
120 | } |
|
121 | } | |
121 | RevlogError::InvalidRevision => { |
|
122 | RevlogError::InvalidRevision => { | |
122 | ListRevTrackedFilesErrorKind::InvalidRevision |
|
123 | ListRevTrackedFilesErrorKind::InvalidRevision | |
123 | } |
|
124 | } | |
124 | RevlogError::AmbiguousPrefix => { |
|
125 | RevlogError::AmbiguousPrefix => { | |
125 | ListRevTrackedFilesErrorKind::AmbiguousPrefix |
|
126 | ListRevTrackedFilesErrorKind::AmbiguousPrefix | |
126 | } |
|
127 | } | |
127 | RevlogError::Corrupted => { |
|
128 | RevlogError::Corrupted => { | |
128 | ListRevTrackedFilesErrorKind::CorruptedRevlog |
|
129 | ListRevTrackedFilesErrorKind::CorruptedRevlog | |
129 | } |
|
130 | } | |
130 | RevlogError::UnknowDataFormat(format) => { |
|
131 | RevlogError::UnknowDataFormat(format) => { | |
131 | ListRevTrackedFilesErrorKind::UnknowRevlogDataFormat(format) |
|
132 | ListRevTrackedFilesErrorKind::UnknowRevlogDataFormat(format) | |
132 | } |
|
133 | } | |
133 | } |
|
134 | } | |
134 | .into() |
|
135 | .into() | |
135 | } |
|
136 | } | |
136 | } |
|
137 | } | |
137 |
|
138 | |||
138 | /// List files under Mercurial control at a given revision. |
|
139 | /// List files under Mercurial control at a given revision. | |
139 | pub struct ListRevTrackedFiles<'a> { |
|
140 | pub struct ListRevTrackedFiles<'a> { | |
140 | /// The revision to list the files from. |
|
141 | /// The revision to list the files from. | |
141 | rev: &'a str, |
|
142 | rev: &'a str, | |
142 | /// The changelog file |
|
143 | /// The changelog file | |
143 | changelog: Changelog, |
|
144 | changelog: Changelog, | |
144 | /// The manifest file |
|
145 | /// The manifest file | |
145 | manifest: Manifest, |
|
146 | manifest: Manifest, | |
146 | /// The manifest entry corresponding to the revision. |
|
147 | /// The manifest entry corresponding to the revision. | |
147 | /// |
|
148 | /// | |
148 | /// Used to hold the owner of the returned references. |
|
149 | /// Used to hold the owner of the returned references. | |
149 | manifest_entry: Option<ManifestEntry>, |
|
150 | manifest_entry: Option<ManifestEntry>, | |
150 | } |
|
151 | } | |
151 |
|
152 | |||
152 | impl<'a> ListRevTrackedFiles<'a> { |
|
153 | impl<'a> ListRevTrackedFiles<'a> { | |
153 | pub fn new( |
|
154 | pub fn new( | |
154 | root: &PathBuf, |
|
155 | root: &PathBuf, | |
155 | rev: &'a str, |
|
156 | rev: &'a str, | |
156 | ) -> Result<Self, ListRevTrackedFilesError> { |
|
157 | ) -> Result<Self, ListRevTrackedFilesError> { | |
157 | let changelog = Changelog::open(&root)?; |
|
158 | let changelog = Changelog::open(&root)?; | |
158 | let manifest = Manifest::open(&root)?; |
|
159 | let manifest = Manifest::open(&root)?; | |
159 |
|
160 | |||
160 | Ok(Self { |
|
161 | Ok(Self { | |
161 | rev, |
|
162 | rev, | |
162 | changelog, |
|
163 | changelog, | |
163 | manifest, |
|
164 | manifest, | |
164 | manifest_entry: None, |
|
165 | manifest_entry: None, | |
165 | }) |
|
166 | }) | |
166 | } |
|
167 | } | |
167 |
|
168 | |||
168 | pub fn run( |
|
169 | pub fn run( | |
169 | &mut self, |
|
170 | &mut self, | |
170 | ) -> Result<impl Iterator<Item = &HgPath>, ListRevTrackedFilesError> { |
|
171 | ) -> Result<impl Iterator<Item = &HgPath>, ListRevTrackedFilesError> { | |
171 | let changelog_entry = match self.rev.parse::<Revision>() { |
|
172 | let changelog_entry = match self.rev.parse::<Revision>() { | |
172 | Ok(rev) => self.changelog.get_rev(rev)?, |
|
173 | Ok(rev) => self.changelog.get_rev(rev)?, | |
173 | _ => { |
|
174 | _ => { | |
174 |
let changelog_node = |
|
175 | let changelog_node = NodePrefix::from_hex(&self.rev) | |
175 | .or(Err(ListRevTrackedFilesErrorKind::InvalidRevision))?; |
|
176 | .or(Err(ListRevTrackedFilesErrorKind::InvalidRevision))?; | |
176 |
self.changelog.get_node( |
|
177 | self.changelog.get_node(changelog_node.borrow())? | |
177 | } |
|
178 | } | |
178 | }; |
|
179 | }; | |
179 |
let manifest_node = |
|
180 | let manifest_node = Node::from_hex(&changelog_entry.manifest_node()?) | |
180 | .or(Err(ListRevTrackedFilesErrorKind::CorruptedRevlog))?; |
|
181 | .or(Err(ListRevTrackedFilesErrorKind::CorruptedRevlog))?; | |
181 |
|
182 | |||
182 | self.manifest_entry = Some(self.manifest.get_node(&manifest_node)?); |
|
183 | self.manifest_entry = | |
|
184 | Some(self.manifest.get_node((&manifest_node).into())?); | |||
183 |
|
185 | |||
184 | if let Some(ref manifest_entry) = self.manifest_entry { |
|
186 | if let Some(ref manifest_entry) = self.manifest_entry { | |
185 | Ok(manifest_entry.files()) |
|
187 | Ok(manifest_entry.files()) | |
186 | } else { |
|
188 | } else { | |
187 | panic!( |
|
189 | panic!( | |
188 | "manifest entry should have been stored in self.manifest_node to ensure its lifetime since references are returned from it" |
|
190 | "manifest entry should have been stored in self.manifest_node to ensure its lifetime since references are returned from it" | |
189 | ) |
|
191 | ) | |
190 | } |
|
192 | } | |
191 | } |
|
193 | } | |
192 | } |
|
194 | } |
@@ -1,58 +1,59 b'' | |||||
1 | use crate::revlog::revlog::{Revlog, RevlogError}; |
|
1 | use crate::revlog::revlog::{Revlog, RevlogError}; | |
|
2 | use crate::revlog::NodePrefixRef; | |||
2 | use crate::revlog::Revision; |
|
3 | use crate::revlog::Revision; | |
3 | use std::path::PathBuf; |
|
4 | use std::path::PathBuf; | |
4 |
|
5 | |||
5 | /// A specialized `Revlog` to work with `changelog` data format. |
|
6 | /// A specialized `Revlog` to work with `changelog` data format. | |
6 | pub struct Changelog { |
|
7 | pub struct Changelog { | |
7 | /// The generic `revlog` format. |
|
8 | /// The generic `revlog` format. | |
8 | revlog: Revlog, |
|
9 | revlog: Revlog, | |
9 | } |
|
10 | } | |
10 |
|
11 | |||
11 | impl Changelog { |
|
12 | impl Changelog { | |
12 | /// Open the `changelog` of a repository given by its root. |
|
13 | /// Open the `changelog` of a repository given by its root. | |
13 | pub fn open(root: &PathBuf) -> Result<Self, RevlogError> { |
|
14 | pub fn open(root: &PathBuf) -> Result<Self, RevlogError> { | |
14 | let index_file = root.join(".hg/store/00changelog.i"); |
|
15 | let index_file = root.join(".hg/store/00changelog.i"); | |
15 | let revlog = Revlog::open(&index_file, None)?; |
|
16 | let revlog = Revlog::open(&index_file, None)?; | |
16 | Ok(Self { revlog }) |
|
17 | Ok(Self { revlog }) | |
17 | } |
|
18 | } | |
18 |
|
19 | |||
19 | /// Return the `ChangelogEntry` a given node id. |
|
20 | /// Return the `ChangelogEntry` a given node id. | |
20 | pub fn get_node( |
|
21 | pub fn get_node( | |
21 | &self, |
|
22 | &self, | |
22 |
node: |
|
23 | node: NodePrefixRef, | |
23 | ) -> Result<ChangelogEntry, RevlogError> { |
|
24 | ) -> Result<ChangelogEntry, RevlogError> { | |
24 | let rev = self.revlog.get_node_rev(node)?; |
|
25 | let rev = self.revlog.get_node_rev(node)?; | |
25 | self.get_rev(rev) |
|
26 | self.get_rev(rev) | |
26 | } |
|
27 | } | |
27 |
|
28 | |||
28 | /// Return the `ChangelogEntry` of a given node revision. |
|
29 | /// Return the `ChangelogEntry` of a given node revision. | |
29 | pub fn get_rev( |
|
30 | pub fn get_rev( | |
30 | &self, |
|
31 | &self, | |
31 | rev: Revision, |
|
32 | rev: Revision, | |
32 | ) -> Result<ChangelogEntry, RevlogError> { |
|
33 | ) -> Result<ChangelogEntry, RevlogError> { | |
33 | let bytes = self.revlog.get_rev_data(rev)?; |
|
34 | let bytes = self.revlog.get_rev_data(rev)?; | |
34 | Ok(ChangelogEntry { bytes }) |
|
35 | Ok(ChangelogEntry { bytes }) | |
35 | } |
|
36 | } | |
36 | } |
|
37 | } | |
37 |
|
38 | |||
38 | /// `Changelog` entry which knows how to interpret the `changelog` data bytes. |
|
39 | /// `Changelog` entry which knows how to interpret the `changelog` data bytes. | |
39 | #[derive(Debug)] |
|
40 | #[derive(Debug)] | |
40 | pub struct ChangelogEntry { |
|
41 | pub struct ChangelogEntry { | |
41 | /// The data bytes of the `changelog` entry. |
|
42 | /// The data bytes of the `changelog` entry. | |
42 | bytes: Vec<u8>, |
|
43 | bytes: Vec<u8>, | |
43 | } |
|
44 | } | |
44 |
|
45 | |||
45 | impl ChangelogEntry { |
|
46 | impl ChangelogEntry { | |
46 | /// Return an iterator over the lines of the entry. |
|
47 | /// Return an iterator over the lines of the entry. | |
47 | pub fn lines(&self) -> impl Iterator<Item = &[u8]> { |
|
48 | pub fn lines(&self) -> impl Iterator<Item = &[u8]> { | |
48 | self.bytes |
|
49 | self.bytes | |
49 | .split(|b| b == &b'\n') |
|
50 | .split(|b| b == &b'\n') | |
50 | .filter(|line| !line.is_empty()) |
|
51 | .filter(|line| !line.is_empty()) | |
51 | } |
|
52 | } | |
52 |
|
53 | |||
53 | /// Return the node id of the `manifest` referenced by this `changelog` |
|
54 | /// Return the node id of the `manifest` referenced by this `changelog` | |
54 | /// entry. |
|
55 | /// entry. | |
55 | pub fn manifest_node(&self) -> Result<&[u8], RevlogError> { |
|
56 | pub fn manifest_node(&self) -> Result<&[u8], RevlogError> { | |
56 | self.lines().next().ok_or(RevlogError::Corrupted) |
|
57 | self.lines().next().ok_or(RevlogError::Corrupted) | |
57 | } |
|
58 | } | |
58 | } |
|
59 | } |
@@ -1,390 +1,392 b'' | |||||
|
1 | use std::convert::TryInto; | |||
1 | use std::ops::Deref; |
|
2 | use std::ops::Deref; | |
2 |
|
3 | |||
3 | use byteorder::{BigEndian, ByteOrder}; |
|
4 | use byteorder::{BigEndian, ByteOrder}; | |
4 |
|
5 | |||
|
6 | use crate::revlog::node::Node; | |||
5 | use crate::revlog::revlog::RevlogError; |
|
7 | use crate::revlog::revlog::RevlogError; | |
6 | use crate::revlog::{Revision, NULL_REVISION}; |
|
8 | use crate::revlog::{Revision, NULL_REVISION}; | |
7 |
|
9 | |||
8 | pub const INDEX_ENTRY_SIZE: usize = 64; |
|
10 | pub const INDEX_ENTRY_SIZE: usize = 64; | |
9 |
|
11 | |||
10 | /// A Revlog index |
|
12 | /// A Revlog index | |
11 | pub struct Index { |
|
13 | pub struct Index { | |
12 | bytes: Box<dyn Deref<Target = [u8]> + Send>, |
|
14 | bytes: Box<dyn Deref<Target = [u8]> + Send>, | |
13 | /// Offsets of starts of index blocks. |
|
15 | /// Offsets of starts of index blocks. | |
14 | /// Only needed when the index is interleaved with data. |
|
16 | /// Only needed when the index is interleaved with data. | |
15 | offsets: Option<Vec<usize>>, |
|
17 | offsets: Option<Vec<usize>>, | |
16 | } |
|
18 | } | |
17 |
|
19 | |||
18 | impl Index { |
|
20 | impl Index { | |
19 | /// Create an index from bytes. |
|
21 | /// Create an index from bytes. | |
20 | /// Calculate the start of each entry when is_inline is true. |
|
22 | /// Calculate the start of each entry when is_inline is true. | |
21 | pub fn new( |
|
23 | pub fn new( | |
22 | bytes: Box<dyn Deref<Target = [u8]> + Send>, |
|
24 | bytes: Box<dyn Deref<Target = [u8]> + Send>, | |
23 | ) -> Result<Self, RevlogError> { |
|
25 | ) -> Result<Self, RevlogError> { | |
24 | if is_inline(&bytes) { |
|
26 | if is_inline(&bytes) { | |
25 | let mut offset: usize = 0; |
|
27 | let mut offset: usize = 0; | |
26 | let mut offsets = Vec::new(); |
|
28 | let mut offsets = Vec::new(); | |
27 |
|
29 | |||
28 | while offset + INDEX_ENTRY_SIZE <= bytes.len() { |
|
30 | while offset + INDEX_ENTRY_SIZE <= bytes.len() { | |
29 | offsets.push(offset); |
|
31 | offsets.push(offset); | |
30 | let end = offset + INDEX_ENTRY_SIZE; |
|
32 | let end = offset + INDEX_ENTRY_SIZE; | |
31 | let entry = IndexEntry { |
|
33 | let entry = IndexEntry { | |
32 | bytes: &bytes[offset..end], |
|
34 | bytes: &bytes[offset..end], | |
33 | offset_override: None, |
|
35 | offset_override: None, | |
34 | }; |
|
36 | }; | |
35 |
|
37 | |||
36 | offset += INDEX_ENTRY_SIZE + entry.compressed_len(); |
|
38 | offset += INDEX_ENTRY_SIZE + entry.compressed_len(); | |
37 | } |
|
39 | } | |
38 |
|
40 | |||
39 | if offset == bytes.len() { |
|
41 | if offset == bytes.len() { | |
40 | Ok(Self { |
|
42 | Ok(Self { | |
41 | bytes, |
|
43 | bytes, | |
42 | offsets: Some(offsets), |
|
44 | offsets: Some(offsets), | |
43 | }) |
|
45 | }) | |
44 | } else { |
|
46 | } else { | |
45 | Err(RevlogError::Corrupted) |
|
47 | Err(RevlogError::Corrupted) | |
46 | } |
|
48 | } | |
47 | } else { |
|
49 | } else { | |
48 | Ok(Self { |
|
50 | Ok(Self { | |
49 | bytes, |
|
51 | bytes, | |
50 | offsets: None, |
|
52 | offsets: None, | |
51 | }) |
|
53 | }) | |
52 | } |
|
54 | } | |
53 | } |
|
55 | } | |
54 |
|
56 | |||
55 | /// Value of the inline flag. |
|
57 | /// Value of the inline flag. | |
56 | pub fn is_inline(&self) -> bool { |
|
58 | pub fn is_inline(&self) -> bool { | |
57 | is_inline(&self.bytes) |
|
59 | is_inline(&self.bytes) | |
58 | } |
|
60 | } | |
59 |
|
61 | |||
60 | /// Return a slice of bytes if `revlog` is inline. Panic if not. |
|
62 | /// Return a slice of bytes if `revlog` is inline. Panic if not. | |
61 | pub fn data(&self, start: usize, end: usize) -> &[u8] { |
|
63 | pub fn data(&self, start: usize, end: usize) -> &[u8] { | |
62 | if !self.is_inline() { |
|
64 | if !self.is_inline() { | |
63 | panic!("tried to access data in the index of a revlog that is not inline"); |
|
65 | panic!("tried to access data in the index of a revlog that is not inline"); | |
64 | } |
|
66 | } | |
65 | &self.bytes[start..end] |
|
67 | &self.bytes[start..end] | |
66 | } |
|
68 | } | |
67 |
|
69 | |||
68 | /// Return number of entries of the revlog index. |
|
70 | /// Return number of entries of the revlog index. | |
69 | pub fn len(&self) -> usize { |
|
71 | pub fn len(&self) -> usize { | |
70 | if let Some(offsets) = &self.offsets { |
|
72 | if let Some(offsets) = &self.offsets { | |
71 | offsets.len() |
|
73 | offsets.len() | |
72 | } else { |
|
74 | } else { | |
73 | self.bytes.len() / INDEX_ENTRY_SIZE |
|
75 | self.bytes.len() / INDEX_ENTRY_SIZE | |
74 | } |
|
76 | } | |
75 | } |
|
77 | } | |
76 |
|
78 | |||
77 | /// Returns `true` if the `Index` has zero `entries`. |
|
79 | /// Returns `true` if the `Index` has zero `entries`. | |
78 | pub fn is_empty(&self) -> bool { |
|
80 | pub fn is_empty(&self) -> bool { | |
79 | self.len() == 0 |
|
81 | self.len() == 0 | |
80 | } |
|
82 | } | |
81 |
|
83 | |||
82 | /// Return the index entry corresponding to the given revision if it |
|
84 | /// Return the index entry corresponding to the given revision if it | |
83 | /// exists. |
|
85 | /// exists. | |
84 | pub fn get_entry(&self, rev: Revision) -> Option<IndexEntry> { |
|
86 | pub fn get_entry(&self, rev: Revision) -> Option<IndexEntry> { | |
85 | if rev == NULL_REVISION { |
|
87 | if rev == NULL_REVISION { | |
86 | return None; |
|
88 | return None; | |
87 | } |
|
89 | } | |
88 | if let Some(offsets) = &self.offsets { |
|
90 | if let Some(offsets) = &self.offsets { | |
89 | self.get_entry_inline(rev, offsets) |
|
91 | self.get_entry_inline(rev, offsets) | |
90 | } else { |
|
92 | } else { | |
91 | self.get_entry_separated(rev) |
|
93 | self.get_entry_separated(rev) | |
92 | } |
|
94 | } | |
93 | } |
|
95 | } | |
94 |
|
96 | |||
95 | fn get_entry_inline( |
|
97 | fn get_entry_inline( | |
96 | &self, |
|
98 | &self, | |
97 | rev: Revision, |
|
99 | rev: Revision, | |
98 | offsets: &[usize], |
|
100 | offsets: &[usize], | |
99 | ) -> Option<IndexEntry> { |
|
101 | ) -> Option<IndexEntry> { | |
100 | let start = *offsets.get(rev as usize)?; |
|
102 | let start = *offsets.get(rev as usize)?; | |
101 | let end = start.checked_add(INDEX_ENTRY_SIZE)?; |
|
103 | let end = start.checked_add(INDEX_ENTRY_SIZE)?; | |
102 | let bytes = &self.bytes[start..end]; |
|
104 | let bytes = &self.bytes[start..end]; | |
103 |
|
105 | |||
104 | // See IndexEntry for an explanation of this override. |
|
106 | // See IndexEntry for an explanation of this override. | |
105 | let offset_override = Some(end); |
|
107 | let offset_override = Some(end); | |
106 |
|
108 | |||
107 | Some(IndexEntry { |
|
109 | Some(IndexEntry { | |
108 | bytes, |
|
110 | bytes, | |
109 | offset_override, |
|
111 | offset_override, | |
110 | }) |
|
112 | }) | |
111 | } |
|
113 | } | |
112 |
|
114 | |||
113 | fn get_entry_separated(&self, rev: Revision) -> Option<IndexEntry> { |
|
115 | fn get_entry_separated(&self, rev: Revision) -> Option<IndexEntry> { | |
114 | let max_rev = self.bytes.len() / INDEX_ENTRY_SIZE; |
|
116 | let max_rev = self.bytes.len() / INDEX_ENTRY_SIZE; | |
115 | if rev as usize >= max_rev { |
|
117 | if rev as usize >= max_rev { | |
116 | return None; |
|
118 | return None; | |
117 | } |
|
119 | } | |
118 | let start = rev as usize * INDEX_ENTRY_SIZE; |
|
120 | let start = rev as usize * INDEX_ENTRY_SIZE; | |
119 | let end = start + INDEX_ENTRY_SIZE; |
|
121 | let end = start + INDEX_ENTRY_SIZE; | |
120 | let bytes = &self.bytes[start..end]; |
|
122 | let bytes = &self.bytes[start..end]; | |
121 |
|
123 | |||
122 | // Override the offset of the first revision as its bytes are used |
|
124 | // Override the offset of the first revision as its bytes are used | |
123 | // for the index's metadata (saving space because it is always 0) |
|
125 | // for the index's metadata (saving space because it is always 0) | |
124 | let offset_override = if rev == 0 { Some(0) } else { None }; |
|
126 | let offset_override = if rev == 0 { Some(0) } else { None }; | |
125 |
|
127 | |||
126 | Some(IndexEntry { |
|
128 | Some(IndexEntry { | |
127 | bytes, |
|
129 | bytes, | |
128 | offset_override, |
|
130 | offset_override, | |
129 | }) |
|
131 | }) | |
130 | } |
|
132 | } | |
131 | } |
|
133 | } | |
132 |
|
134 | |||
133 | #[derive(Debug)] |
|
135 | #[derive(Debug)] | |
134 | pub struct IndexEntry<'a> { |
|
136 | pub struct IndexEntry<'a> { | |
135 | bytes: &'a [u8], |
|
137 | bytes: &'a [u8], | |
136 | /// Allows to override the offset value of the entry. |
|
138 | /// Allows to override the offset value of the entry. | |
137 | /// |
|
139 | /// | |
138 | /// For interleaved index and data, the offset stored in the index |
|
140 | /// For interleaved index and data, the offset stored in the index | |
139 | /// corresponds to the separated data offset. |
|
141 | /// corresponds to the separated data offset. | |
140 | /// It has to be overridden with the actual offset in the interleaved |
|
142 | /// It has to be overridden with the actual offset in the interleaved | |
141 | /// index which is just after the index block. |
|
143 | /// index which is just after the index block. | |
142 | /// |
|
144 | /// | |
143 | /// For separated index and data, the offset stored in the first index |
|
145 | /// For separated index and data, the offset stored in the first index | |
144 | /// entry is mixed with the index headers. |
|
146 | /// entry is mixed with the index headers. | |
145 | /// It has to be overridden with 0. |
|
147 | /// It has to be overridden with 0. | |
146 | offset_override: Option<usize>, |
|
148 | offset_override: Option<usize>, | |
147 | } |
|
149 | } | |
148 |
|
150 | |||
149 | impl<'a> IndexEntry<'a> { |
|
151 | impl<'a> IndexEntry<'a> { | |
150 | /// Return the offset of the data. |
|
152 | /// Return the offset of the data. | |
151 | pub fn offset(&self) -> usize { |
|
153 | pub fn offset(&self) -> usize { | |
152 | if let Some(offset_override) = self.offset_override { |
|
154 | if let Some(offset_override) = self.offset_override { | |
153 | offset_override |
|
155 | offset_override | |
154 | } else { |
|
156 | } else { | |
155 | let mut bytes = [0; 8]; |
|
157 | let mut bytes = [0; 8]; | |
156 | bytes[2..8].copy_from_slice(&self.bytes[0..=5]); |
|
158 | bytes[2..8].copy_from_slice(&self.bytes[0..=5]); | |
157 | BigEndian::read_u64(&bytes[..]) as usize |
|
159 | BigEndian::read_u64(&bytes[..]) as usize | |
158 | } |
|
160 | } | |
159 | } |
|
161 | } | |
160 |
|
162 | |||
161 | /// Return the compressed length of the data. |
|
163 | /// Return the compressed length of the data. | |
162 | pub fn compressed_len(&self) -> usize { |
|
164 | pub fn compressed_len(&self) -> usize { | |
163 | BigEndian::read_u32(&self.bytes[8..=11]) as usize |
|
165 | BigEndian::read_u32(&self.bytes[8..=11]) as usize | |
164 | } |
|
166 | } | |
165 |
|
167 | |||
166 | /// Return the uncompressed length of the data. |
|
168 | /// Return the uncompressed length of the data. | |
167 | pub fn uncompressed_len(&self) -> usize { |
|
169 | pub fn uncompressed_len(&self) -> usize { | |
168 | BigEndian::read_u32(&self.bytes[12..=15]) as usize |
|
170 | BigEndian::read_u32(&self.bytes[12..=15]) as usize | |
169 | } |
|
171 | } | |
170 |
|
172 | |||
171 | /// Return the revision upon which the data has been derived. |
|
173 | /// Return the revision upon which the data has been derived. | |
172 | pub fn base_revision(&self) -> Revision { |
|
174 | pub fn base_revision(&self) -> Revision { | |
173 | // TODO Maybe return an Option when base_revision == rev? |
|
175 | // TODO Maybe return an Option when base_revision == rev? | |
174 | // Requires to add rev to IndexEntry |
|
176 | // Requires to add rev to IndexEntry | |
175 |
|
177 | |||
176 | BigEndian::read_i32(&self.bytes[16..]) |
|
178 | BigEndian::read_i32(&self.bytes[16..]) | |
177 | } |
|
179 | } | |
178 |
|
180 | |||
179 | pub fn p1(&self) -> Revision { |
|
181 | pub fn p1(&self) -> Revision { | |
180 | BigEndian::read_i32(&self.bytes[24..]) |
|
182 | BigEndian::read_i32(&self.bytes[24..]) | |
181 | } |
|
183 | } | |
182 |
|
184 | |||
183 | pub fn p2(&self) -> Revision { |
|
185 | pub fn p2(&self) -> Revision { | |
184 | BigEndian::read_i32(&self.bytes[28..]) |
|
186 | BigEndian::read_i32(&self.bytes[28..]) | |
185 | } |
|
187 | } | |
186 |
|
188 | |||
187 | /// Return the hash of revision's full text. |
|
189 | /// Return the hash of revision's full text. | |
188 | /// |
|
190 | /// | |
189 | /// Currently, SHA-1 is used and only the first 20 bytes of this field |
|
191 | /// Currently, SHA-1 is used and only the first 20 bytes of this field | |
190 | /// are used. |
|
192 | /// are used. | |
191 |
pub fn hash(&self) -> & |
|
193 | pub fn hash(&self) -> &Node { | |
192 | &self.bytes[32..52] |
|
194 | (&self.bytes[32..52]).try_into().unwrap() | |
193 | } |
|
195 | } | |
194 | } |
|
196 | } | |
195 |
|
197 | |||
196 | /// Value of the inline flag. |
|
198 | /// Value of the inline flag. | |
197 | pub fn is_inline(index_bytes: &[u8]) -> bool { |
|
199 | pub fn is_inline(index_bytes: &[u8]) -> bool { | |
198 | match &index_bytes[0..=1] { |
|
200 | match &index_bytes[0..=1] { | |
199 | [0, 0] | [0, 2] => false, |
|
201 | [0, 0] | [0, 2] => false, | |
200 | _ => true, |
|
202 | _ => true, | |
201 | } |
|
203 | } | |
202 | } |
|
204 | } | |
203 |
|
205 | |||
204 | #[cfg(test)] |
|
206 | #[cfg(test)] | |
205 | mod tests { |
|
207 | mod tests { | |
206 | use super::*; |
|
208 | use super::*; | |
207 |
|
209 | |||
208 | #[cfg(test)] |
|
210 | #[cfg(test)] | |
209 | #[derive(Debug, Copy, Clone)] |
|
211 | #[derive(Debug, Copy, Clone)] | |
210 | pub struct IndexEntryBuilder { |
|
212 | pub struct IndexEntryBuilder { | |
211 | is_first: bool, |
|
213 | is_first: bool, | |
212 | is_inline: bool, |
|
214 | is_inline: bool, | |
213 | is_general_delta: bool, |
|
215 | is_general_delta: bool, | |
214 | version: u16, |
|
216 | version: u16, | |
215 | offset: usize, |
|
217 | offset: usize, | |
216 | compressed_len: usize, |
|
218 | compressed_len: usize, | |
217 | uncompressed_len: usize, |
|
219 | uncompressed_len: usize, | |
218 | base_revision: Revision, |
|
220 | base_revision: Revision, | |
219 | } |
|
221 | } | |
220 |
|
222 | |||
221 | #[cfg(test)] |
|
223 | #[cfg(test)] | |
222 | impl IndexEntryBuilder { |
|
224 | impl IndexEntryBuilder { | |
223 | pub fn new() -> Self { |
|
225 | pub fn new() -> Self { | |
224 | Self { |
|
226 | Self { | |
225 | is_first: false, |
|
227 | is_first: false, | |
226 | is_inline: false, |
|
228 | is_inline: false, | |
227 | is_general_delta: true, |
|
229 | is_general_delta: true, | |
228 | version: 2, |
|
230 | version: 2, | |
229 | offset: 0, |
|
231 | offset: 0, | |
230 | compressed_len: 0, |
|
232 | compressed_len: 0, | |
231 | uncompressed_len: 0, |
|
233 | uncompressed_len: 0, | |
232 | base_revision: 0, |
|
234 | base_revision: 0, | |
233 | } |
|
235 | } | |
234 | } |
|
236 | } | |
235 |
|
237 | |||
236 | pub fn is_first(&mut self, value: bool) -> &mut Self { |
|
238 | pub fn is_first(&mut self, value: bool) -> &mut Self { | |
237 | self.is_first = value; |
|
239 | self.is_first = value; | |
238 | self |
|
240 | self | |
239 | } |
|
241 | } | |
240 |
|
242 | |||
241 | pub fn with_inline(&mut self, value: bool) -> &mut Self { |
|
243 | pub fn with_inline(&mut self, value: bool) -> &mut Self { | |
242 | self.is_inline = value; |
|
244 | self.is_inline = value; | |
243 | self |
|
245 | self | |
244 | } |
|
246 | } | |
245 |
|
247 | |||
246 | pub fn with_general_delta(&mut self, value: bool) -> &mut Self { |
|
248 | pub fn with_general_delta(&mut self, value: bool) -> &mut Self { | |
247 | self.is_general_delta = value; |
|
249 | self.is_general_delta = value; | |
248 | self |
|
250 | self | |
249 | } |
|
251 | } | |
250 |
|
252 | |||
251 | pub fn with_version(&mut self, value: u16) -> &mut Self { |
|
253 | pub fn with_version(&mut self, value: u16) -> &mut Self { | |
252 | self.version = value; |
|
254 | self.version = value; | |
253 | self |
|
255 | self | |
254 | } |
|
256 | } | |
255 |
|
257 | |||
256 | pub fn with_offset(&mut self, value: usize) -> &mut Self { |
|
258 | pub fn with_offset(&mut self, value: usize) -> &mut Self { | |
257 | self.offset = value; |
|
259 | self.offset = value; | |
258 | self |
|
260 | self | |
259 | } |
|
261 | } | |
260 |
|
262 | |||
261 | pub fn with_compressed_len(&mut self, value: usize) -> &mut Self { |
|
263 | pub fn with_compressed_len(&mut self, value: usize) -> &mut Self { | |
262 | self.compressed_len = value; |
|
264 | self.compressed_len = value; | |
263 | self |
|
265 | self | |
264 | } |
|
266 | } | |
265 |
|
267 | |||
266 | pub fn with_uncompressed_len(&mut self, value: usize) -> &mut Self { |
|
268 | pub fn with_uncompressed_len(&mut self, value: usize) -> &mut Self { | |
267 | self.uncompressed_len = value; |
|
269 | self.uncompressed_len = value; | |
268 | self |
|
270 | self | |
269 | } |
|
271 | } | |
270 |
|
272 | |||
271 | pub fn with_base_revision(&mut self, value: Revision) -> &mut Self { |
|
273 | pub fn with_base_revision(&mut self, value: Revision) -> &mut Self { | |
272 | self.base_revision = value; |
|
274 | self.base_revision = value; | |
273 | self |
|
275 | self | |
274 | } |
|
276 | } | |
275 |
|
277 | |||
276 | pub fn build(&self) -> Vec<u8> { |
|
278 | pub fn build(&self) -> Vec<u8> { | |
277 | let mut bytes = Vec::with_capacity(INDEX_ENTRY_SIZE); |
|
279 | let mut bytes = Vec::with_capacity(INDEX_ENTRY_SIZE); | |
278 | if self.is_first { |
|
280 | if self.is_first { | |
279 | bytes.extend(&match (self.is_general_delta, self.is_inline) { |
|
281 | bytes.extend(&match (self.is_general_delta, self.is_inline) { | |
280 | (false, false) => [0u8, 0], |
|
282 | (false, false) => [0u8, 0], | |
281 | (false, true) => [0u8, 1], |
|
283 | (false, true) => [0u8, 1], | |
282 | (true, false) => [0u8, 2], |
|
284 | (true, false) => [0u8, 2], | |
283 | (true, true) => [0u8, 3], |
|
285 | (true, true) => [0u8, 3], | |
284 | }); |
|
286 | }); | |
285 | bytes.extend(&self.version.to_be_bytes()); |
|
287 | bytes.extend(&self.version.to_be_bytes()); | |
286 | // Remaining offset bytes. |
|
288 | // Remaining offset bytes. | |
287 | bytes.extend(&[0u8; 2]); |
|
289 | bytes.extend(&[0u8; 2]); | |
288 | } else { |
|
290 | } else { | |
289 | // Offset is only 6 bytes will usize is 8. |
|
291 | // Offset is only 6 bytes will usize is 8. | |
290 | bytes.extend(&self.offset.to_be_bytes()[2..]); |
|
292 | bytes.extend(&self.offset.to_be_bytes()[2..]); | |
291 | } |
|
293 | } | |
292 | bytes.extend(&[0u8; 2]); // Revision flags. |
|
294 | bytes.extend(&[0u8; 2]); // Revision flags. | |
293 | bytes.extend(&self.compressed_len.to_be_bytes()[4..]); |
|
295 | bytes.extend(&self.compressed_len.to_be_bytes()[4..]); | |
294 | bytes.extend(&self.uncompressed_len.to_be_bytes()[4..]); |
|
296 | bytes.extend(&self.uncompressed_len.to_be_bytes()[4..]); | |
295 | bytes.extend(&self.base_revision.to_be_bytes()); |
|
297 | bytes.extend(&self.base_revision.to_be_bytes()); | |
296 | bytes |
|
298 | bytes | |
297 | } |
|
299 | } | |
298 | } |
|
300 | } | |
299 |
|
301 | |||
300 | #[test] |
|
302 | #[test] | |
301 | fn is_not_inline_when_no_inline_flag_test() { |
|
303 | fn is_not_inline_when_no_inline_flag_test() { | |
302 | let bytes = IndexEntryBuilder::new() |
|
304 | let bytes = IndexEntryBuilder::new() | |
303 | .is_first(true) |
|
305 | .is_first(true) | |
304 | .with_general_delta(false) |
|
306 | .with_general_delta(false) | |
305 | .with_inline(false) |
|
307 | .with_inline(false) | |
306 | .build(); |
|
308 | .build(); | |
307 |
|
309 | |||
308 | assert_eq!(is_inline(&bytes), false) |
|
310 | assert_eq!(is_inline(&bytes), false) | |
309 | } |
|
311 | } | |
310 |
|
312 | |||
311 | #[test] |
|
313 | #[test] | |
312 | fn is_inline_when_inline_flag_test() { |
|
314 | fn is_inline_when_inline_flag_test() { | |
313 | let bytes = IndexEntryBuilder::new() |
|
315 | let bytes = IndexEntryBuilder::new() | |
314 | .is_first(true) |
|
316 | .is_first(true) | |
315 | .with_general_delta(false) |
|
317 | .with_general_delta(false) | |
316 | .with_inline(true) |
|
318 | .with_inline(true) | |
317 | .build(); |
|
319 | .build(); | |
318 |
|
320 | |||
319 | assert_eq!(is_inline(&bytes), true) |
|
321 | assert_eq!(is_inline(&bytes), true) | |
320 | } |
|
322 | } | |
321 |
|
323 | |||
322 | #[test] |
|
324 | #[test] | |
323 | fn is_inline_when_inline_and_generaldelta_flags_test() { |
|
325 | fn is_inline_when_inline_and_generaldelta_flags_test() { | |
324 | let bytes = IndexEntryBuilder::new() |
|
326 | let bytes = IndexEntryBuilder::new() | |
325 | .is_first(true) |
|
327 | .is_first(true) | |
326 | .with_general_delta(true) |
|
328 | .with_general_delta(true) | |
327 | .with_inline(true) |
|
329 | .with_inline(true) | |
328 | .build(); |
|
330 | .build(); | |
329 |
|
331 | |||
330 | assert_eq!(is_inline(&bytes), true) |
|
332 | assert_eq!(is_inline(&bytes), true) | |
331 | } |
|
333 | } | |
332 |
|
334 | |||
333 | #[test] |
|
335 | #[test] | |
334 | fn test_offset() { |
|
336 | fn test_offset() { | |
335 | let bytes = IndexEntryBuilder::new().with_offset(1).build(); |
|
337 | let bytes = IndexEntryBuilder::new().with_offset(1).build(); | |
336 | let entry = IndexEntry { |
|
338 | let entry = IndexEntry { | |
337 | bytes: &bytes, |
|
339 | bytes: &bytes, | |
338 | offset_override: None, |
|
340 | offset_override: None, | |
339 | }; |
|
341 | }; | |
340 |
|
342 | |||
341 | assert_eq!(entry.offset(), 1) |
|
343 | assert_eq!(entry.offset(), 1) | |
342 | } |
|
344 | } | |
343 |
|
345 | |||
344 | #[test] |
|
346 | #[test] | |
345 | fn test_with_overridden_offset() { |
|
347 | fn test_with_overridden_offset() { | |
346 | let bytes = IndexEntryBuilder::new().with_offset(1).build(); |
|
348 | let bytes = IndexEntryBuilder::new().with_offset(1).build(); | |
347 | let entry = IndexEntry { |
|
349 | let entry = IndexEntry { | |
348 | bytes: &bytes, |
|
350 | bytes: &bytes, | |
349 | offset_override: Some(2), |
|
351 | offset_override: Some(2), | |
350 | }; |
|
352 | }; | |
351 |
|
353 | |||
352 | assert_eq!(entry.offset(), 2) |
|
354 | assert_eq!(entry.offset(), 2) | |
353 | } |
|
355 | } | |
354 |
|
356 | |||
355 | #[test] |
|
357 | #[test] | |
356 | fn test_compressed_len() { |
|
358 | fn test_compressed_len() { | |
357 | let bytes = IndexEntryBuilder::new().with_compressed_len(1).build(); |
|
359 | let bytes = IndexEntryBuilder::new().with_compressed_len(1).build(); | |
358 | let entry = IndexEntry { |
|
360 | let entry = IndexEntry { | |
359 | bytes: &bytes, |
|
361 | bytes: &bytes, | |
360 | offset_override: None, |
|
362 | offset_override: None, | |
361 | }; |
|
363 | }; | |
362 |
|
364 | |||
363 | assert_eq!(entry.compressed_len(), 1) |
|
365 | assert_eq!(entry.compressed_len(), 1) | |
364 | } |
|
366 | } | |
365 |
|
367 | |||
366 | #[test] |
|
368 | #[test] | |
367 | fn test_uncompressed_len() { |
|
369 | fn test_uncompressed_len() { | |
368 | let bytes = IndexEntryBuilder::new().with_uncompressed_len(1).build(); |
|
370 | let bytes = IndexEntryBuilder::new().with_uncompressed_len(1).build(); | |
369 | let entry = IndexEntry { |
|
371 | let entry = IndexEntry { | |
370 | bytes: &bytes, |
|
372 | bytes: &bytes, | |
371 | offset_override: None, |
|
373 | offset_override: None, | |
372 | }; |
|
374 | }; | |
373 |
|
375 | |||
374 | assert_eq!(entry.uncompressed_len(), 1) |
|
376 | assert_eq!(entry.uncompressed_len(), 1) | |
375 | } |
|
377 | } | |
376 |
|
378 | |||
377 | #[test] |
|
379 | #[test] | |
378 | fn test_base_revision() { |
|
380 | fn test_base_revision() { | |
379 | let bytes = IndexEntryBuilder::new().with_base_revision(1).build(); |
|
381 | let bytes = IndexEntryBuilder::new().with_base_revision(1).build(); | |
380 | let entry = IndexEntry { |
|
382 | let entry = IndexEntry { | |
381 | bytes: &bytes, |
|
383 | bytes: &bytes, | |
382 | offset_override: None, |
|
384 | offset_override: None, | |
383 | }; |
|
385 | }; | |
384 |
|
386 | |||
385 | assert_eq!(entry.base_revision(), 1) |
|
387 | assert_eq!(entry.base_revision(), 1) | |
386 | } |
|
388 | } | |
387 | } |
|
389 | } | |
388 |
|
390 | |||
389 | #[cfg(test)] |
|
391 | #[cfg(test)] | |
390 | pub use tests::IndexEntryBuilder; |
|
392 | pub use tests::IndexEntryBuilder; |
@@ -1,73 +1,77 b'' | |||||
1 | use crate::revlog::revlog::{Revlog, RevlogError}; |
|
1 | use crate::revlog::revlog::{Revlog, RevlogError}; | |
|
2 | use crate::revlog::NodePrefixRef; | |||
2 | use crate::revlog::Revision; |
|
3 | use crate::revlog::Revision; | |
3 | use crate::utils::hg_path::HgPath; |
|
4 | use crate::utils::hg_path::HgPath; | |
4 | use std::path::PathBuf; |
|
5 | use std::path::PathBuf; | |
5 |
|
6 | |||
6 | /// A specialized `Revlog` to work with `manifest` data format. |
|
7 | /// A specialized `Revlog` to work with `manifest` data format. | |
7 | pub struct Manifest { |
|
8 | pub struct Manifest { | |
8 | /// The generic `revlog` format. |
|
9 | /// The generic `revlog` format. | |
9 | revlog: Revlog, |
|
10 | revlog: Revlog, | |
10 | } |
|
11 | } | |
11 |
|
12 | |||
12 | impl Manifest { |
|
13 | impl Manifest { | |
13 | /// Open the `manifest` of a repository given by its root. |
|
14 | /// Open the `manifest` of a repository given by its root. | |
14 | pub fn open(root: &PathBuf) -> Result<Self, RevlogError> { |
|
15 | pub fn open(root: &PathBuf) -> Result<Self, RevlogError> { | |
15 | let index_file = root.join(".hg/store/00manifest.i"); |
|
16 | let index_file = root.join(".hg/store/00manifest.i"); | |
16 | let revlog = Revlog::open(&index_file, None)?; |
|
17 | let revlog = Revlog::open(&index_file, None)?; | |
17 | Ok(Self { revlog }) |
|
18 | Ok(Self { revlog }) | |
18 | } |
|
19 | } | |
19 |
|
20 | |||
20 | /// Return the `ManifestEntry` of a given node id. |
|
21 | /// Return the `ManifestEntry` of a given node id. | |
21 | pub fn get_node(&self, node: &[u8]) -> Result<ManifestEntry, RevlogError> { |
|
22 | pub fn get_node( | |
|
23 | &self, | |||
|
24 | node: NodePrefixRef, | |||
|
25 | ) -> Result<ManifestEntry, RevlogError> { | |||
22 | let rev = self.revlog.get_node_rev(node)?; |
|
26 | let rev = self.revlog.get_node_rev(node)?; | |
23 | self.get_rev(rev) |
|
27 | self.get_rev(rev) | |
24 | } |
|
28 | } | |
25 |
|
29 | |||
26 | /// Return the `ManifestEntry` of a given node revision. |
|
30 | /// Return the `ManifestEntry` of a given node revision. | |
27 | pub fn get_rev( |
|
31 | pub fn get_rev( | |
28 | &self, |
|
32 | &self, | |
29 | rev: Revision, |
|
33 | rev: Revision, | |
30 | ) -> Result<ManifestEntry, RevlogError> { |
|
34 | ) -> Result<ManifestEntry, RevlogError> { | |
31 | let bytes = self.revlog.get_rev_data(rev)?; |
|
35 | let bytes = self.revlog.get_rev_data(rev)?; | |
32 | Ok(ManifestEntry { bytes }) |
|
36 | Ok(ManifestEntry { bytes }) | |
33 | } |
|
37 | } | |
34 | } |
|
38 | } | |
35 |
|
39 | |||
36 | /// `Manifest` entry which knows how to interpret the `manifest` data bytes. |
|
40 | /// `Manifest` entry which knows how to interpret the `manifest` data bytes. | |
37 | #[derive(Debug)] |
|
41 | #[derive(Debug)] | |
38 | pub struct ManifestEntry { |
|
42 | pub struct ManifestEntry { | |
39 | bytes: Vec<u8>, |
|
43 | bytes: Vec<u8>, | |
40 | } |
|
44 | } | |
41 |
|
45 | |||
42 | impl ManifestEntry { |
|
46 | impl ManifestEntry { | |
43 | /// Return an iterator over the lines of the entry. |
|
47 | /// Return an iterator over the lines of the entry. | |
44 | pub fn lines(&self) -> impl Iterator<Item = &[u8]> { |
|
48 | pub fn lines(&self) -> impl Iterator<Item = &[u8]> { | |
45 | self.bytes |
|
49 | self.bytes | |
46 | .split(|b| b == &b'\n') |
|
50 | .split(|b| b == &b'\n') | |
47 | .filter(|line| !line.is_empty()) |
|
51 | .filter(|line| !line.is_empty()) | |
48 | } |
|
52 | } | |
49 |
|
53 | |||
50 | /// Return an iterator over the files of the entry. |
|
54 | /// Return an iterator over the files of the entry. | |
51 | pub fn files(&self) -> impl Iterator<Item = &HgPath> { |
|
55 | pub fn files(&self) -> impl Iterator<Item = &HgPath> { | |
52 | self.lines().filter(|line| !line.is_empty()).map(|line| { |
|
56 | self.lines().filter(|line| !line.is_empty()).map(|line| { | |
53 | let pos = line |
|
57 | let pos = line | |
54 | .iter() |
|
58 | .iter() | |
55 | .position(|x| x == &b'\0') |
|
59 | .position(|x| x == &b'\0') | |
56 | .expect("manifest line should contain \\0"); |
|
60 | .expect("manifest line should contain \\0"); | |
57 | HgPath::new(&line[..pos]) |
|
61 | HgPath::new(&line[..pos]) | |
58 | }) |
|
62 | }) | |
59 | } |
|
63 | } | |
60 |
|
64 | |||
61 | /// Return an iterator over the files of the entry. |
|
65 | /// Return an iterator over the files of the entry. | |
62 | pub fn files_with_nodes(&self) -> impl Iterator<Item = (&HgPath, &[u8])> { |
|
66 | pub fn files_with_nodes(&self) -> impl Iterator<Item = (&HgPath, &[u8])> { | |
63 | self.lines().filter(|line| !line.is_empty()).map(|line| { |
|
67 | self.lines().filter(|line| !line.is_empty()).map(|line| { | |
64 | let pos = line |
|
68 | let pos = line | |
65 | .iter() |
|
69 | .iter() | |
66 | .position(|x| x == &b'\0') |
|
70 | .position(|x| x == &b'\0') | |
67 | .expect("manifest line should contain \\0"); |
|
71 | .expect("manifest line should contain \\0"); | |
68 | let hash_start = pos + 1; |
|
72 | let hash_start = pos + 1; | |
69 | let hash_end = hash_start + 40; |
|
73 | let hash_end = hash_start + 40; | |
70 | (HgPath::new(&line[..pos]), &line[hash_start..hash_end]) |
|
74 | (HgPath::new(&line[..pos]), &line[hash_start..hash_end]) | |
71 | }) |
|
75 | }) | |
72 | } |
|
76 | } | |
73 | } |
|
77 | } |
@@ -1,438 +1,456 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 hex::{self, FromHex, FromHexError}; |
|
11 | use hex::{self, FromHex, FromHexError}; | |
|
12 | use std::convert::{TryFrom, TryInto}; | |||
12 |
|
13 | |||
13 | /// The length in bytes of a `Node` |
|
14 | /// The length in bytes of a `Node` | |
14 | /// |
|
15 | /// | |
15 | /// This constant is meant to ease refactors of this module, and |
|
16 | /// This constant is meant to ease refactors of this module, and | |
16 | /// are private so that calling code does not expect all nodes have |
|
17 | /// are private so that calling code does not expect all nodes have | |
17 | /// the same size, should we support several formats concurrently in |
|
18 | /// the same size, should we support several formats concurrently in | |
18 | /// the future. |
|
19 | /// the future. | |
19 | pub const NODE_BYTES_LENGTH: usize = 20; |
|
20 | pub const NODE_BYTES_LENGTH: usize = 20; | |
20 |
|
21 | |||
21 | /// Id of the null node. |
|
22 | /// Id of the null node. | |
22 | /// |
|
23 | /// | |
23 | /// Used to indicate the absence of node. |
|
24 | /// Used to indicate the absence of node. | |
24 | pub const NULL_NODE_ID: [u8; NODE_BYTES_LENGTH] = [0u8; NODE_BYTES_LENGTH]; |
|
25 | pub const NULL_NODE_ID: [u8; NODE_BYTES_LENGTH] = [0u8; NODE_BYTES_LENGTH]; | |
25 |
|
26 | |||
26 | /// The length in bytes of a `Node` |
|
27 | /// The length in bytes of a `Node` | |
27 | /// |
|
28 | /// | |
28 | /// see also `NODES_BYTES_LENGTH` about it being private. |
|
29 | /// see also `NODES_BYTES_LENGTH` about it being private. | |
29 | const NODE_NYBBLES_LENGTH: usize = 2 * NODE_BYTES_LENGTH; |
|
30 | const NODE_NYBBLES_LENGTH: usize = 2 * NODE_BYTES_LENGTH; | |
30 |
|
31 | |||
31 | /// Private alias for readability and to ease future change |
|
32 | /// Private alias for readability and to ease future change | |
32 | type NodeData = [u8; NODE_BYTES_LENGTH]; |
|
33 | type NodeData = [u8; NODE_BYTES_LENGTH]; | |
33 |
|
34 | |||
34 | /// Binary revision SHA |
|
35 | /// Binary revision SHA | |
35 | /// |
|
36 | /// | |
36 | /// ## Future changes of hash size |
|
37 | /// ## Future changes of hash size | |
37 | /// |
|
38 | /// | |
38 | /// To accomodate future changes of hash size, Rust callers |
|
39 | /// To accomodate future changes of hash size, Rust callers | |
39 | /// should use the conversion methods at the boundaries (FFI, actual |
|
40 | /// should use the conversion methods at the boundaries (FFI, actual | |
40 | /// computation of hashes and I/O) only, and only if required. |
|
41 | /// computation of hashes and I/O) only, and only if required. | |
41 | /// |
|
42 | /// | |
42 | /// All other callers outside of unit tests should just handle `Node` values |
|
43 | /// All other callers outside of unit tests should just handle `Node` values | |
43 | /// and never make any assumption on the actual length, using [`nybbles_len`] |
|
44 | /// and never make any assumption on the actual length, using [`nybbles_len`] | |
44 | /// if they need a loop boundary. |
|
45 | /// if they need a loop boundary. | |
45 | /// |
|
46 | /// | |
46 | /// All methods that create a `Node` either take a type that enforces |
|
47 | /// All methods that create a `Node` either take a type that enforces | |
47 | /// the size or fail immediately at runtime with [`ExactLengthRequired`]. |
|
48 | /// the size or fail immediately at runtime with [`ExactLengthRequired`]. | |
48 | /// |
|
49 | /// | |
49 | /// [`nybbles_len`]: #method.nybbles_len |
|
50 | /// [`nybbles_len`]: #method.nybbles_len | |
50 | /// [`ExactLengthRequired`]: struct.NodeError#variant.ExactLengthRequired |
|
51 | /// [`ExactLengthRequired`]: struct.NodeError#variant.ExactLengthRequired | |
51 | #[derive(Clone, Debug, PartialEq)] |
|
52 | #[derive(Clone, Debug, PartialEq)] | |
52 | #[repr(transparent)] |
|
53 | #[repr(transparent)] | |
53 | pub struct Node { |
|
54 | pub struct Node { | |
54 | data: NodeData, |
|
55 | data: NodeData, | |
55 | } |
|
56 | } | |
56 |
|
57 | |||
57 | /// The node value for NULL_REVISION |
|
58 | /// The node value for NULL_REVISION | |
58 | pub const NULL_NODE: Node = Node { |
|
59 | pub const NULL_NODE: Node = Node { | |
59 | data: [0; NODE_BYTES_LENGTH], |
|
60 | data: [0; NODE_BYTES_LENGTH], | |
60 | }; |
|
61 | }; | |
61 |
|
62 | |||
62 | impl From<NodeData> for Node { |
|
63 | impl From<NodeData> for Node { | |
63 | fn from(data: NodeData) -> Node { |
|
64 | fn from(data: NodeData) -> Node { | |
64 | Node { data } |
|
65 | Node { data } | |
65 | } |
|
66 | } | |
66 | } |
|
67 | } | |
67 |
|
68 | |||
|
69 | /// Return an error if the slice has an unexpected length | |||
|
70 | impl<'a> TryFrom<&'a [u8]> for &'a Node { | |||
|
71 | type Error = std::array::TryFromSliceError; | |||
|
72 | ||||
|
73 | #[inline] | |||
|
74 | fn try_from(bytes: &'a [u8]) -> Result<&'a Node, Self::Error> { | |||
|
75 | let data = bytes.try_into()?; | |||
|
76 | // Safety: `#[repr(transparent)]` makes it ok to "wrap" the target | |||
|
77 | // of a reference to the type of the single field. | |||
|
78 | Ok(unsafe { std::mem::transmute::<&NodeData, &Node>(data) }) | |||
|
79 | } | |||
|
80 | } | |||
|
81 | ||||
68 | #[derive(Debug, PartialEq)] |
|
82 | #[derive(Debug, PartialEq)] | |
69 | pub enum NodeError { |
|
83 | pub enum NodeError { | |
70 | ExactLengthRequired(usize, String), |
|
84 | ExactLengthRequired(usize, String), | |
71 | PrefixTooLong(String), |
|
85 | PrefixTooLong(String), | |
72 | HexError(FromHexError, String), |
|
86 | HexError(FromHexError, String), | |
73 | } |
|
87 | } | |
74 |
|
88 | |||
75 | /// Low level utility function, also for prefixes |
|
89 | /// Low level utility function, also for prefixes | |
76 | fn get_nybble(s: &[u8], i: usize) -> u8 { |
|
90 | fn get_nybble(s: &[u8], i: usize) -> u8 { | |
77 | if i % 2 == 0 { |
|
91 | if i % 2 == 0 { | |
78 | s[i / 2] >> 4 |
|
92 | s[i / 2] >> 4 | |
79 | } else { |
|
93 | } else { | |
80 | s[i / 2] & 0x0f |
|
94 | s[i / 2] & 0x0f | |
81 | } |
|
95 | } | |
82 | } |
|
96 | } | |
83 |
|
97 | |||
84 | impl Node { |
|
98 | impl Node { | |
85 | /// Retrieve the `i`th half-byte of the binary data. |
|
99 | /// Retrieve the `i`th half-byte of the binary data. | |
86 | /// |
|
100 | /// | |
87 | /// This is also the `i`th hexadecimal digit in numeric form, |
|
101 | /// This is also the `i`th hexadecimal digit in numeric form, | |
88 | /// also called a [nybble](https://en.wikipedia.org/wiki/Nibble). |
|
102 | /// also called a [nybble](https://en.wikipedia.org/wiki/Nibble). | |
89 | pub fn get_nybble(&self, i: usize) -> u8 { |
|
103 | pub fn get_nybble(&self, i: usize) -> u8 { | |
90 | get_nybble(&self.data, i) |
|
104 | get_nybble(&self.data, i) | |
91 | } |
|
105 | } | |
92 |
|
106 | |||
93 | /// Length of the data, in nybbles |
|
107 | /// Length of the data, in nybbles | |
94 | pub fn nybbles_len(&self) -> usize { |
|
108 | pub fn nybbles_len(&self) -> usize { | |
95 | // public exposure as an instance method only, so that we can |
|
109 | // public exposure as an instance method only, so that we can | |
96 | // easily support several sizes of hashes if needed in the future. |
|
110 | // easily support several sizes of hashes if needed in the future. | |
97 | NODE_NYBBLES_LENGTH |
|
111 | NODE_NYBBLES_LENGTH | |
98 | } |
|
112 | } | |
99 |
|
113 | |||
100 | /// Convert from hexadecimal string representation |
|
114 | /// Convert from hexadecimal string representation | |
101 | /// |
|
115 | /// | |
102 | /// Exact length is required. |
|
116 | /// Exact length is required. | |
103 | /// |
|
117 | /// | |
104 | /// To be used in FFI and I/O only, in order to facilitate future |
|
118 | /// To be used in FFI and I/O only, in order to facilitate future | |
105 | /// changes of hash format. |
|
119 | /// changes of hash format. | |
106 |
pub fn from_hex(hex: |
|
120 | pub fn from_hex(hex: impl AsRef<[u8]>) -> Result<Node, NodeError> { | |
107 | Ok(NodeData::from_hex(hex) |
|
121 | Ok(NodeData::from_hex(hex.as_ref()) | |
108 | .map_err(|e| NodeError::from((e, hex)))? |
|
122 | .map_err(|e| NodeError::from((e, hex)))? | |
109 | .into()) |
|
123 | .into()) | |
110 | } |
|
124 | } | |
111 |
|
125 | |||
112 | /// Convert to hexadecimal string representation |
|
126 | /// Convert to hexadecimal string representation | |
113 | /// |
|
127 | /// | |
114 | /// To be used in FFI and I/O only, in order to facilitate future |
|
128 | /// To be used in FFI and I/O only, in order to facilitate future | |
115 | /// changes of hash format. |
|
129 | /// changes of hash format. | |
116 | pub fn encode_hex(&self) -> String { |
|
130 | pub fn encode_hex(&self) -> String { | |
117 | hex::encode(self.data) |
|
131 | hex::encode(self.data) | |
118 | } |
|
132 | } | |
119 |
|
133 | |||
120 | /// Provide access to binary data |
|
134 | /// Provide access to binary data | |
121 | /// |
|
135 | /// | |
122 | /// This is needed by FFI layers, for instance to return expected |
|
136 | /// This is needed by FFI layers, for instance to return expected | |
123 | /// binary values to Python. |
|
137 | /// binary values to Python. | |
124 | pub fn as_bytes(&self) -> &[u8] { |
|
138 | pub fn as_bytes(&self) -> &[u8] { | |
125 | &self.data |
|
139 | &self.data | |
126 | } |
|
140 | } | |
127 | } |
|
141 | } | |
128 |
|
142 | |||
129 |
impl<T: AsRef< |
|
143 | impl<T: AsRef<[u8]>> From<(FromHexError, T)> for NodeError { | |
130 | fn from(err_offender: (FromHexError, T)) -> Self { |
|
144 | fn from(err_offender: (FromHexError, T)) -> Self { | |
131 | let (err, offender) = err_offender; |
|
145 | let (err, offender) = err_offender; | |
|
146 | let offender = String::from_utf8_lossy(offender.as_ref()).into_owned(); | |||
132 | match err { |
|
147 | match err { | |
133 | FromHexError::InvalidStringLength => { |
|
148 | FromHexError::InvalidStringLength => { | |
134 | NodeError::ExactLengthRequired( |
|
149 | NodeError::ExactLengthRequired(NODE_NYBBLES_LENGTH, offender) | |
135 | NODE_NYBBLES_LENGTH, |
|
|||
136 | offender.as_ref().to_owned(), |
|
|||
137 | ) |
|
|||
138 | } |
|
150 | } | |
139 |
_ => NodeError::HexError(err, offender |
|
151 | _ => NodeError::HexError(err, offender), | |
140 | } |
|
152 | } | |
141 | } |
|
153 | } | |
142 | } |
|
154 | } | |
143 |
|
155 | |||
144 | /// The beginning of a binary revision SHA. |
|
156 | /// The beginning of a binary revision SHA. | |
145 | /// |
|
157 | /// | |
146 | /// Since it can potentially come from an hexadecimal representation with |
|
158 | /// Since it can potentially come from an hexadecimal representation with | |
147 | /// odd length, it needs to carry around whether the last 4 bits are relevant |
|
159 | /// odd length, it needs to carry around whether the last 4 bits are relevant | |
148 | /// or not. |
|
160 | /// or not. | |
149 | #[derive(Debug, PartialEq)] |
|
161 | #[derive(Debug, PartialEq)] | |
150 | pub struct NodePrefix { |
|
162 | pub struct NodePrefix { | |
151 | buf: Vec<u8>, |
|
163 | buf: Vec<u8>, | |
152 | is_odd: bool, |
|
164 | is_odd: bool, | |
153 | } |
|
165 | } | |
154 |
|
166 | |||
155 | impl NodePrefix { |
|
167 | impl NodePrefix { | |
156 | /// Convert from hexadecimal string representation |
|
168 | /// Convert from hexadecimal string representation | |
157 | /// |
|
169 | /// | |
158 | /// Similarly to `hex::decode`, can be used with Unicode string types |
|
170 | /// Similarly to `hex::decode`, can be used with Unicode string types | |
159 | /// (`String`, `&str`) as well as bytes. |
|
171 | /// (`String`, `&str`) as well as bytes. | |
160 | /// |
|
172 | /// | |
161 | /// To be used in FFI and I/O only, in order to facilitate future |
|
173 | /// To be used in FFI and I/O only, in order to facilitate future | |
162 | /// changes of hash format. |
|
174 | /// changes of hash format. | |
163 | pub fn from_hex(hex: impl AsRef<[u8]>) -> Result<Self, NodeError> { |
|
175 | pub fn from_hex(hex: impl AsRef<[u8]>) -> Result<Self, NodeError> { | |
164 | let hex = hex.as_ref(); |
|
176 | let hex = hex.as_ref(); | |
165 | let len = hex.len(); |
|
177 | let len = hex.len(); | |
166 | if len > NODE_NYBBLES_LENGTH { |
|
178 | if len > NODE_NYBBLES_LENGTH { | |
167 | return Err(NodeError::PrefixTooLong( |
|
179 | return Err(NodeError::PrefixTooLong( | |
168 | String::from_utf8_lossy(hex).to_owned().to_string(), |
|
180 | String::from_utf8_lossy(hex).to_owned().to_string(), | |
169 | )); |
|
181 | )); | |
170 | } |
|
182 | } | |
171 |
|
183 | |||
172 | let is_odd = len % 2 == 1; |
|
184 | let is_odd = len % 2 == 1; | |
173 | let even_part = if is_odd { &hex[..len - 1] } else { hex }; |
|
185 | let even_part = if is_odd { &hex[..len - 1] } else { hex }; | |
174 |
let mut buf: Vec<u8> = |
|
186 | let mut buf: Vec<u8> = | |
175 | .map_err(|e| (e, String::from_utf8_lossy(hex)))?; |
|
187 | Vec::from_hex(&even_part).map_err(|e| (e, hex))?; | |
176 |
|
188 | |||
177 | if is_odd { |
|
189 | if is_odd { | |
178 | let latest_char = char::from(hex[len - 1]); |
|
190 | let latest_char = char::from(hex[len - 1]); | |
179 | let latest_nybble = latest_char.to_digit(16).ok_or_else(|| { |
|
191 | let latest_nybble = latest_char.to_digit(16).ok_or_else(|| { | |
180 | ( |
|
192 | ( | |
181 | FromHexError::InvalidHexCharacter { |
|
193 | FromHexError::InvalidHexCharacter { | |
182 | c: latest_char, |
|
194 | c: latest_char, | |
183 | index: len - 1, |
|
195 | index: len - 1, | |
184 | }, |
|
196 | }, | |
185 |
|
|
197 | hex, | |
186 | ) |
|
198 | ) | |
187 | })? as u8; |
|
199 | })? as u8; | |
188 | buf.push(latest_nybble << 4); |
|
200 | buf.push(latest_nybble << 4); | |
189 | } |
|
201 | } | |
190 | Ok(NodePrefix { buf, is_odd }) |
|
202 | Ok(NodePrefix { buf, is_odd }) | |
191 | } |
|
203 | } | |
192 |
|
204 | |||
193 | pub fn borrow(&self) -> NodePrefixRef { |
|
205 | pub fn borrow(&self) -> NodePrefixRef { | |
194 | NodePrefixRef { |
|
206 | NodePrefixRef { | |
195 | buf: &self.buf, |
|
207 | buf: &self.buf, | |
196 | is_odd: self.is_odd, |
|
208 | is_odd: self.is_odd, | |
197 | } |
|
209 | } | |
198 | } |
|
210 | } | |
199 | } |
|
211 | } | |
200 |
|
212 | |||
201 | #[derive(Clone, Debug, PartialEq)] |
|
213 | #[derive(Clone, Debug, PartialEq)] | |
202 | pub struct NodePrefixRef<'a> { |
|
214 | pub struct NodePrefixRef<'a> { | |
203 | buf: &'a [u8], |
|
215 | buf: &'a [u8], | |
204 | is_odd: bool, |
|
216 | is_odd: bool, | |
205 | } |
|
217 | } | |
206 |
|
218 | |||
207 | impl<'a> NodePrefixRef<'a> { |
|
219 | impl<'a> NodePrefixRef<'a> { | |
208 | pub fn len(&self) -> usize { |
|
220 | pub fn len(&self) -> usize { | |
209 | if self.is_odd { |
|
221 | if self.is_odd { | |
210 | self.buf.len() * 2 - 1 |
|
222 | self.buf.len() * 2 - 1 | |
211 | } else { |
|
223 | } else { | |
212 | self.buf.len() * 2 |
|
224 | self.buf.len() * 2 | |
213 | } |
|
225 | } | |
214 | } |
|
226 | } | |
215 |
|
227 | |||
216 | pub fn is_empty(&self) -> bool { |
|
228 | pub fn is_empty(&self) -> bool { | |
217 | self.len() == 0 |
|
229 | self.len() == 0 | |
218 | } |
|
230 | } | |
219 |
|
231 | |||
220 | pub fn is_prefix_of(&self, node: &Node) -> bool { |
|
232 | pub fn is_prefix_of(&self, node: &Node) -> bool { | |
221 | if self.is_odd { |
|
233 | if self.is_odd { | |
222 | let buf = self.buf; |
|
234 | let buf = self.buf; | |
223 | let last_pos = buf.len() - 1; |
|
235 | let last_pos = buf.len() - 1; | |
224 | node.data.starts_with(buf.split_at(last_pos).0) |
|
236 | node.data.starts_with(buf.split_at(last_pos).0) | |
225 | && node.data[last_pos] >> 4 == buf[last_pos] >> 4 |
|
237 | && node.data[last_pos] >> 4 == buf[last_pos] >> 4 | |
226 | } else { |
|
238 | } else { | |
227 | node.data.starts_with(self.buf) |
|
239 | node.data.starts_with(self.buf) | |
228 | } |
|
240 | } | |
229 | } |
|
241 | } | |
230 |
|
242 | |||
231 | /// Retrieve the `i`th half-byte from the prefix. |
|
243 | /// Retrieve the `i`th half-byte from the prefix. | |
232 | /// |
|
244 | /// | |
233 | /// This is also the `i`th hexadecimal digit in numeric form, |
|
245 | /// This is also the `i`th hexadecimal digit in numeric form, | |
234 | /// also called a [nybble](https://en.wikipedia.org/wiki/Nibble). |
|
246 | /// also called a [nybble](https://en.wikipedia.org/wiki/Nibble). | |
235 | pub fn get_nybble(&self, i: usize) -> u8 { |
|
247 | pub fn get_nybble(&self, i: usize) -> u8 { | |
236 | assert!(i < self.len()); |
|
248 | assert!(i < self.len()); | |
237 | get_nybble(self.buf, i) |
|
249 | get_nybble(self.buf, i) | |
238 | } |
|
250 | } | |
239 |
|
251 | |||
240 | /// Return the index first nybble that's different from `node` |
|
252 | /// Return the index first nybble that's different from `node` | |
241 | /// |
|
253 | /// | |
242 | /// If the return value is `None` that means that `self` is |
|
254 | /// If the return value is `None` that means that `self` is | |
243 | /// a prefix of `node`, but the current method is a bit slower |
|
255 | /// a prefix of `node`, but the current method is a bit slower | |
244 | /// than `is_prefix_of`. |
|
256 | /// than `is_prefix_of`. | |
245 | /// |
|
257 | /// | |
246 | /// Returned index is as in `get_nybble`, i.e., starting at 0. |
|
258 | /// Returned index is as in `get_nybble`, i.e., starting at 0. | |
247 | pub fn first_different_nybble(&self, node: &Node) -> Option<usize> { |
|
259 | pub fn first_different_nybble(&self, node: &Node) -> Option<usize> { | |
248 | let buf = self.buf; |
|
260 | let buf = self.buf; | |
249 | let until = if self.is_odd { |
|
261 | let until = if self.is_odd { | |
250 | buf.len() - 1 |
|
262 | buf.len() - 1 | |
251 | } else { |
|
263 | } else { | |
252 | buf.len() |
|
264 | buf.len() | |
253 | }; |
|
265 | }; | |
254 | for (i, item) in buf.iter().enumerate().take(until) { |
|
266 | for (i, item) in buf.iter().enumerate().take(until) { | |
255 | if *item != node.data[i] { |
|
267 | if *item != node.data[i] { | |
256 | return if *item & 0xf0 == node.data[i] & 0xf0 { |
|
268 | return if *item & 0xf0 == node.data[i] & 0xf0 { | |
257 | Some(2 * i + 1) |
|
269 | Some(2 * i + 1) | |
258 | } else { |
|
270 | } else { | |
259 | Some(2 * i) |
|
271 | Some(2 * i) | |
260 | }; |
|
272 | }; | |
261 | } |
|
273 | } | |
262 | } |
|
274 | } | |
263 | if self.is_odd && buf[until] & 0xf0 != node.data[until] & 0xf0 { |
|
275 | if self.is_odd && buf[until] & 0xf0 != node.data[until] & 0xf0 { | |
264 | Some(until * 2) |
|
276 | Some(until * 2) | |
265 | } else { |
|
277 | } else { | |
266 | None |
|
278 | None | |
267 | } |
|
279 | } | |
268 | } |
|
280 | } | |
269 | } |
|
281 | } | |
270 |
|
282 | |||
271 | /// A shortcut for full `Node` references |
|
283 | /// A shortcut for full `Node` references | |
272 | impl<'a> From<&'a Node> for NodePrefixRef<'a> { |
|
284 | impl<'a> From<&'a Node> for NodePrefixRef<'a> { | |
273 | fn from(node: &'a Node) -> Self { |
|
285 | fn from(node: &'a Node) -> Self { | |
274 | NodePrefixRef { |
|
286 | NodePrefixRef { | |
275 | buf: &node.data, |
|
287 | buf: &node.data, | |
276 | is_odd: false, |
|
288 | is_odd: false, | |
277 | } |
|
289 | } | |
278 | } |
|
290 | } | |
279 | } |
|
291 | } | |
280 |
|
292 | |||
|
293 | impl PartialEq<Node> for NodePrefixRef<'_> { | |||
|
294 | fn eq(&self, other: &Node) -> bool { | |||
|
295 | !self.is_odd && self.buf == other.data | |||
|
296 | } | |||
|
297 | } | |||
|
298 | ||||
281 | #[cfg(test)] |
|
299 | #[cfg(test)] | |
282 | mod tests { |
|
300 | mod tests { | |
283 | use super::*; |
|
301 | use super::*; | |
284 |
|
302 | |||
285 | fn sample_node() -> Node { |
|
303 | fn sample_node() -> Node { | |
286 | let mut data = [0; NODE_BYTES_LENGTH]; |
|
304 | let mut data = [0; NODE_BYTES_LENGTH]; | |
287 | data.copy_from_slice(&[ |
|
305 | data.copy_from_slice(&[ | |
288 | 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, |
|
306 | 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, | |
289 | 0x98, 0x76, 0x54, 0x32, 0x10, 0xde, 0xad, 0xbe, 0xef, |
|
307 | 0x98, 0x76, 0x54, 0x32, 0x10, 0xde, 0xad, 0xbe, 0xef, | |
290 | ]); |
|
308 | ]); | |
291 | data.into() |
|
309 | data.into() | |
292 | } |
|
310 | } | |
293 |
|
311 | |||
294 | /// Pad an hexadecimal string to reach `NODE_NYBBLES_LENGTH` |
|
312 | /// Pad an hexadecimal string to reach `NODE_NYBBLES_LENGTH` | |
295 | /// |
|
313 | ///check_hash | |
296 | /// The padding is made with zeros |
|
314 | /// The padding is made with zeros | |
297 | pub fn hex_pad_right(hex: &str) -> String { |
|
315 | pub fn hex_pad_right(hex: &str) -> String { | |
298 | let mut res = hex.to_string(); |
|
316 | let mut res = hex.to_string(); | |
299 | while res.len() < NODE_NYBBLES_LENGTH { |
|
317 | while res.len() < NODE_NYBBLES_LENGTH { | |
300 | res.push('0'); |
|
318 | res.push('0'); | |
301 | } |
|
319 | } | |
302 | res |
|
320 | res | |
303 | } |
|
321 | } | |
304 |
|
322 | |||
305 | fn sample_node_hex() -> String { |
|
323 | fn sample_node_hex() -> String { | |
306 | hex_pad_right("0123456789abcdeffedcba9876543210deadbeef") |
|
324 | hex_pad_right("0123456789abcdeffedcba9876543210deadbeef") | |
307 | } |
|
325 | } | |
308 |
|
326 | |||
309 | #[test] |
|
327 | #[test] | |
310 | fn test_node_from_hex() { |
|
328 | fn test_node_from_hex() { | |
311 | assert_eq!(Node::from_hex(&sample_node_hex()), Ok(sample_node())); |
|
329 | assert_eq!(Node::from_hex(&sample_node_hex()), Ok(sample_node())); | |
312 |
|
330 | |||
313 | let mut short = hex_pad_right("0123"); |
|
331 | let mut short = hex_pad_right("0123"); | |
314 | short.pop(); |
|
332 | short.pop(); | |
315 | short.pop(); |
|
333 | short.pop(); | |
316 | assert_eq!( |
|
334 | assert_eq!( | |
317 | Node::from_hex(&short), |
|
335 | Node::from_hex(&short), | |
318 | Err(NodeError::ExactLengthRequired(NODE_NYBBLES_LENGTH, short)), |
|
336 | Err(NodeError::ExactLengthRequired(NODE_NYBBLES_LENGTH, short)), | |
319 | ); |
|
337 | ); | |
320 |
|
338 | |||
321 | let not_hex = hex_pad_right("012... oops"); |
|
339 | let not_hex = hex_pad_right("012... oops"); | |
322 | assert_eq!( |
|
340 | assert_eq!( | |
323 | Node::from_hex(¬_hex), |
|
341 | Node::from_hex(¬_hex), | |
324 | Err(NodeError::HexError( |
|
342 | Err(NodeError::HexError( | |
325 | FromHexError::InvalidHexCharacter { c: '.', index: 3 }, |
|
343 | FromHexError::InvalidHexCharacter { c: '.', index: 3 }, | |
326 | not_hex, |
|
344 | not_hex, | |
327 | )), |
|
345 | )), | |
328 | ); |
|
346 | ); | |
329 | } |
|
347 | } | |
330 |
|
348 | |||
331 | #[test] |
|
349 | #[test] | |
332 | fn test_node_encode_hex() { |
|
350 | fn test_node_encode_hex() { | |
333 | assert_eq!(sample_node().encode_hex(), sample_node_hex()); |
|
351 | assert_eq!(sample_node().encode_hex(), sample_node_hex()); | |
334 | } |
|
352 | } | |
335 |
|
353 | |||
336 | #[test] |
|
354 | #[test] | |
337 | fn test_prefix_from_hex() -> Result<(), NodeError> { |
|
355 | fn test_prefix_from_hex() -> Result<(), NodeError> { | |
338 | assert_eq!( |
|
356 | assert_eq!( | |
339 | NodePrefix::from_hex("0e1")?, |
|
357 | NodePrefix::from_hex("0e1")?, | |
340 | NodePrefix { |
|
358 | NodePrefix { | |
341 | buf: vec![14, 16], |
|
359 | buf: vec![14, 16], | |
342 | is_odd: true |
|
360 | is_odd: true | |
343 | } |
|
361 | } | |
344 | ); |
|
362 | ); | |
345 | assert_eq!( |
|
363 | assert_eq!( | |
346 | NodePrefix::from_hex("0e1a")?, |
|
364 | NodePrefix::from_hex("0e1a")?, | |
347 | NodePrefix { |
|
365 | NodePrefix { | |
348 | buf: vec![14, 26], |
|
366 | buf: vec![14, 26], | |
349 | is_odd: false |
|
367 | is_odd: false | |
350 | } |
|
368 | } | |
351 | ); |
|
369 | ); | |
352 |
|
370 | |||
353 | // checking limit case |
|
371 | // checking limit case | |
354 | let node_as_vec = sample_node().data.iter().cloned().collect(); |
|
372 | let node_as_vec = sample_node().data.iter().cloned().collect(); | |
355 | assert_eq!( |
|
373 | assert_eq!( | |
356 | NodePrefix::from_hex(sample_node_hex())?, |
|
374 | NodePrefix::from_hex(sample_node_hex())?, | |
357 | NodePrefix { |
|
375 | NodePrefix { | |
358 | buf: node_as_vec, |
|
376 | buf: node_as_vec, | |
359 | is_odd: false |
|
377 | is_odd: false | |
360 | } |
|
378 | } | |
361 | ); |
|
379 | ); | |
362 |
|
380 | |||
363 | Ok(()) |
|
381 | Ok(()) | |
364 | } |
|
382 | } | |
365 |
|
383 | |||
366 | #[test] |
|
384 | #[test] | |
367 | fn test_prefix_from_hex_errors() { |
|
385 | fn test_prefix_from_hex_errors() { | |
368 | assert_eq!( |
|
386 | assert_eq!( | |
369 | NodePrefix::from_hex("testgr"), |
|
387 | NodePrefix::from_hex("testgr"), | |
370 | Err(NodeError::HexError( |
|
388 | Err(NodeError::HexError( | |
371 | FromHexError::InvalidHexCharacter { c: 't', index: 0 }, |
|
389 | FromHexError::InvalidHexCharacter { c: 't', index: 0 }, | |
372 | "testgr".to_string() |
|
390 | "testgr".to_string() | |
373 | )) |
|
391 | )) | |
374 | ); |
|
392 | ); | |
375 | let mut long = NULL_NODE.encode_hex(); |
|
393 | let mut long = NULL_NODE.encode_hex(); | |
376 | long.push('c'); |
|
394 | long.push('c'); | |
377 | match NodePrefix::from_hex(&long) |
|
395 | match NodePrefix::from_hex(&long) | |
378 | .expect_err("should be refused as too long") |
|
396 | .expect_err("should be refused as too long") | |
379 | { |
|
397 | { | |
380 | NodeError::PrefixTooLong(s) => assert_eq!(s, long), |
|
398 | NodeError::PrefixTooLong(s) => assert_eq!(s, long), | |
381 | err => panic!(format!("Should have been TooLong, got {:?}", err)), |
|
399 | err => panic!(format!("Should have been TooLong, got {:?}", err)), | |
382 | } |
|
400 | } | |
383 | } |
|
401 | } | |
384 |
|
402 | |||
385 | #[test] |
|
403 | #[test] | |
386 | fn test_is_prefix_of() -> Result<(), NodeError> { |
|
404 | fn test_is_prefix_of() -> Result<(), NodeError> { | |
387 | let mut node_data = [0; NODE_BYTES_LENGTH]; |
|
405 | let mut node_data = [0; NODE_BYTES_LENGTH]; | |
388 | node_data[0] = 0x12; |
|
406 | node_data[0] = 0x12; | |
389 | node_data[1] = 0xca; |
|
407 | node_data[1] = 0xca; | |
390 | let node = Node::from(node_data); |
|
408 | let node = Node::from(node_data); | |
391 | assert!(NodePrefix::from_hex("12")?.borrow().is_prefix_of(&node)); |
|
409 | assert!(NodePrefix::from_hex("12")?.borrow().is_prefix_of(&node)); | |
392 | assert!(!NodePrefix::from_hex("1a")?.borrow().is_prefix_of(&node)); |
|
410 | assert!(!NodePrefix::from_hex("1a")?.borrow().is_prefix_of(&node)); | |
393 | assert!(NodePrefix::from_hex("12c")?.borrow().is_prefix_of(&node)); |
|
411 | assert!(NodePrefix::from_hex("12c")?.borrow().is_prefix_of(&node)); | |
394 | assert!(!NodePrefix::from_hex("12d")?.borrow().is_prefix_of(&node)); |
|
412 | assert!(!NodePrefix::from_hex("12d")?.borrow().is_prefix_of(&node)); | |
395 | Ok(()) |
|
413 | Ok(()) | |
396 | } |
|
414 | } | |
397 |
|
415 | |||
398 | #[test] |
|
416 | #[test] | |
399 | fn test_get_nybble() -> Result<(), NodeError> { |
|
417 | fn test_get_nybble() -> Result<(), NodeError> { | |
400 | let prefix = NodePrefix::from_hex("dead6789cafe")?; |
|
418 | let prefix = NodePrefix::from_hex("dead6789cafe")?; | |
401 | assert_eq!(prefix.borrow().get_nybble(0), 13); |
|
419 | assert_eq!(prefix.borrow().get_nybble(0), 13); | |
402 | assert_eq!(prefix.borrow().get_nybble(7), 9); |
|
420 | assert_eq!(prefix.borrow().get_nybble(7), 9); | |
403 | Ok(()) |
|
421 | Ok(()) | |
404 | } |
|
422 | } | |
405 |
|
423 | |||
406 | #[test] |
|
424 | #[test] | |
407 | fn test_first_different_nybble_even_prefix() { |
|
425 | fn test_first_different_nybble_even_prefix() { | |
408 | let prefix = NodePrefix::from_hex("12ca").unwrap(); |
|
426 | let prefix = NodePrefix::from_hex("12ca").unwrap(); | |
409 | let prefref = prefix.borrow(); |
|
427 | let prefref = prefix.borrow(); | |
410 | let mut node = Node::from([0; NODE_BYTES_LENGTH]); |
|
428 | let mut node = Node::from([0; NODE_BYTES_LENGTH]); | |
411 | assert_eq!(prefref.first_different_nybble(&node), Some(0)); |
|
429 | assert_eq!(prefref.first_different_nybble(&node), Some(0)); | |
412 | node.data[0] = 0x13; |
|
430 | node.data[0] = 0x13; | |
413 | assert_eq!(prefref.first_different_nybble(&node), Some(1)); |
|
431 | assert_eq!(prefref.first_different_nybble(&node), Some(1)); | |
414 | node.data[0] = 0x12; |
|
432 | node.data[0] = 0x12; | |
415 | assert_eq!(prefref.first_different_nybble(&node), Some(2)); |
|
433 | assert_eq!(prefref.first_different_nybble(&node), Some(2)); | |
416 | node.data[1] = 0xca; |
|
434 | node.data[1] = 0xca; | |
417 | // now it is a prefix |
|
435 | // now it is a prefix | |
418 | assert_eq!(prefref.first_different_nybble(&node), None); |
|
436 | assert_eq!(prefref.first_different_nybble(&node), None); | |
419 | } |
|
437 | } | |
420 |
|
438 | |||
421 | #[test] |
|
439 | #[test] | |
422 | fn test_first_different_nybble_odd_prefix() { |
|
440 | fn test_first_different_nybble_odd_prefix() { | |
423 | let prefix = NodePrefix::from_hex("12c").unwrap(); |
|
441 | let prefix = NodePrefix::from_hex("12c").unwrap(); | |
424 | let prefref = prefix.borrow(); |
|
442 | let prefref = prefix.borrow(); | |
425 | let mut node = Node::from([0; NODE_BYTES_LENGTH]); |
|
443 | let mut node = Node::from([0; NODE_BYTES_LENGTH]); | |
426 | assert_eq!(prefref.first_different_nybble(&node), Some(0)); |
|
444 | assert_eq!(prefref.first_different_nybble(&node), Some(0)); | |
427 | node.data[0] = 0x13; |
|
445 | node.data[0] = 0x13; | |
428 | assert_eq!(prefref.first_different_nybble(&node), Some(1)); |
|
446 | assert_eq!(prefref.first_different_nybble(&node), Some(1)); | |
429 | node.data[0] = 0x12; |
|
447 | node.data[0] = 0x12; | |
430 | assert_eq!(prefref.first_different_nybble(&node), Some(2)); |
|
448 | assert_eq!(prefref.first_different_nybble(&node), Some(2)); | |
431 | node.data[1] = 0xca; |
|
449 | node.data[1] = 0xca; | |
432 | // now it is a prefix |
|
450 | // now it is a prefix | |
433 | assert_eq!(prefref.first_different_nybble(&node), None); |
|
451 | assert_eq!(prefref.first_different_nybble(&node), None); | |
434 | } |
|
452 | } | |
435 | } |
|
453 | } | |
436 |
|
454 | |||
437 | #[cfg(test)] |
|
455 | #[cfg(test)] | |
438 | pub use tests::hex_pad_right; |
|
456 | pub use tests::hex_pad_right; |
@@ -1,344 +1,347 b'' | |||||
1 | use std::borrow::Cow; |
|
1 | use std::borrow::Cow; | |
2 | use std::fs::File; |
|
2 | use std::fs::File; | |
3 | use std::io::Read; |
|
3 | use std::io::Read; | |
4 | use std::ops::Deref; |
|
4 | use std::ops::Deref; | |
5 | use std::path::Path; |
|
5 | use std::path::Path; | |
6 |
|
6 | |||
7 | use byteorder::{BigEndian, ByteOrder}; |
|
7 | use byteorder::{BigEndian, ByteOrder}; | |
8 | use crypto::digest::Digest; |
|
8 | use crypto::digest::Digest; | |
9 | use crypto::sha1::Sha1; |
|
9 | use crypto::sha1::Sha1; | |
10 | use flate2::read::ZlibDecoder; |
|
10 | use flate2::read::ZlibDecoder; | |
11 | use memmap::{Mmap, MmapOptions}; |
|
11 | use memmap::{Mmap, MmapOptions}; | |
12 | use micro_timer::timed; |
|
12 | use micro_timer::timed; | |
13 | use zstd; |
|
13 | use zstd; | |
14 |
|
14 | |||
15 | use super::index::Index; |
|
15 | use super::index::Index; | |
16 |
use super::node::{NODE_BYTES_LENGTH, NULL_NODE |
|
16 | use super::node::{NodePrefixRef, NODE_BYTES_LENGTH, NULL_NODE}; | |
17 | use super::patch; |
|
17 | use super::patch; | |
18 | use crate::revlog::Revision; |
|
18 | use crate::revlog::Revision; | |
19 |
|
19 | |||
20 | pub enum RevlogError { |
|
20 | pub enum RevlogError { | |
21 | IoError(std::io::Error), |
|
21 | IoError(std::io::Error), | |
22 | UnsuportedVersion(u16), |
|
22 | UnsuportedVersion(u16), | |
23 | InvalidRevision, |
|
23 | InvalidRevision, | |
24 | /// Found more than one entry whose ID match the requested prefix |
|
24 | /// Found more than one entry whose ID match the requested prefix | |
25 | AmbiguousPrefix, |
|
25 | AmbiguousPrefix, | |
26 | Corrupted, |
|
26 | Corrupted, | |
27 | UnknowDataFormat(u8), |
|
27 | UnknowDataFormat(u8), | |
28 | } |
|
28 | } | |
29 |
|
29 | |||
30 | fn mmap_open(path: &Path) -> Result<Mmap, std::io::Error> { |
|
30 | fn mmap_open(path: &Path) -> Result<Mmap, std::io::Error> { | |
31 | let file = File::open(path)?; |
|
31 | let file = File::open(path)?; | |
32 | let mmap = unsafe { MmapOptions::new().map(&file) }?; |
|
32 | let mmap = unsafe { MmapOptions::new().map(&file) }?; | |
33 | Ok(mmap) |
|
33 | Ok(mmap) | |
34 | } |
|
34 | } | |
35 |
|
35 | |||
36 | /// Read only implementation of revlog. |
|
36 | /// Read only implementation of revlog. | |
37 | pub struct Revlog { |
|
37 | pub struct Revlog { | |
38 | /// When index and data are not interleaved: bytes of the revlog index. |
|
38 | /// When index and data are not interleaved: bytes of the revlog index. | |
39 | /// When index and data are interleaved: bytes of the revlog index and |
|
39 | /// When index and data are interleaved: bytes of the revlog index and | |
40 | /// data. |
|
40 | /// data. | |
41 | index: Index, |
|
41 | index: Index, | |
42 | /// When index and data are not interleaved: bytes of the revlog data |
|
42 | /// When index and data are not interleaved: bytes of the revlog data | |
43 | data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>>, |
|
43 | data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>>, | |
44 | } |
|
44 | } | |
45 |
|
45 | |||
46 | impl Revlog { |
|
46 | impl Revlog { | |
47 | /// Open a revlog index file. |
|
47 | /// Open a revlog index file. | |
48 | /// |
|
48 | /// | |
49 | /// It will also open the associated data file if index and data are not |
|
49 | /// It will also open the associated data file if index and data are not | |
50 | /// interleaved. |
|
50 | /// interleaved. | |
51 | #[timed] |
|
51 | #[timed] | |
52 | pub fn open( |
|
52 | pub fn open( | |
53 | index_path: &Path, |
|
53 | index_path: &Path, | |
54 | data_path: Option<&Path>, |
|
54 | data_path: Option<&Path>, | |
55 | ) -> Result<Self, RevlogError> { |
|
55 | ) -> Result<Self, RevlogError> { | |
56 | let index_mmap = |
|
56 | let index_mmap = | |
57 | mmap_open(&index_path).map_err(RevlogError::IoError)?; |
|
57 | mmap_open(&index_path).map_err(RevlogError::IoError)?; | |
58 |
|
58 | |||
59 | let version = get_version(&index_mmap); |
|
59 | let version = get_version(&index_mmap); | |
60 | if version != 1 { |
|
60 | if version != 1 { | |
61 | return Err(RevlogError::UnsuportedVersion(version)); |
|
61 | return Err(RevlogError::UnsuportedVersion(version)); | |
62 | } |
|
62 | } | |
63 |
|
63 | |||
64 | let index = Index::new(Box::new(index_mmap))?; |
|
64 | let index = Index::new(Box::new(index_mmap))?; | |
65 |
|
65 | |||
66 | let default_data_path = index_path.with_extension("d"); |
|
66 | let default_data_path = index_path.with_extension("d"); | |
67 |
|
67 | |||
68 | // type annotation required |
|
68 | // type annotation required | |
69 | // won't recognize Mmap as Deref<Target = [u8]> |
|
69 | // won't recognize Mmap as Deref<Target = [u8]> | |
70 | let data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>> = |
|
70 | let data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>> = | |
71 | if index.is_inline() { |
|
71 | if index.is_inline() { | |
72 | None |
|
72 | None | |
73 | } else { |
|
73 | } else { | |
74 | let data_path = data_path.unwrap_or(&default_data_path); |
|
74 | let data_path = data_path.unwrap_or(&default_data_path); | |
75 | let data_mmap = |
|
75 | let data_mmap = | |
76 | mmap_open(data_path).map_err(RevlogError::IoError)?; |
|
76 | mmap_open(data_path).map_err(RevlogError::IoError)?; | |
77 | Some(Box::new(data_mmap)) |
|
77 | Some(Box::new(data_mmap)) | |
78 | }; |
|
78 | }; | |
79 |
|
79 | |||
80 | Ok(Revlog { index, data_bytes }) |
|
80 | Ok(Revlog { index, data_bytes }) | |
81 | } |
|
81 | } | |
82 |
|
82 | |||
83 | /// Return number of entries of the `Revlog`. |
|
83 | /// Return number of entries of the `Revlog`. | |
84 | pub fn len(&self) -> usize { |
|
84 | pub fn len(&self) -> usize { | |
85 | self.index.len() |
|
85 | self.index.len() | |
86 | } |
|
86 | } | |
87 |
|
87 | |||
88 | /// Returns `true` if the `Revlog` has zero `entries`. |
|
88 | /// Returns `true` if the `Revlog` has zero `entries`. | |
89 | pub fn is_empty(&self) -> bool { |
|
89 | pub fn is_empty(&self) -> bool { | |
90 | self.index.is_empty() |
|
90 | self.index.is_empty() | |
91 | } |
|
91 | } | |
92 |
|
92 | |||
93 | /// Return the full data associated to a node. |
|
93 | /// Return the full data associated to a node. | |
94 | #[timed] |
|
94 | #[timed] | |
95 | pub fn get_node_rev(&self, node: &[u8]) -> Result<Revision, RevlogError> { |
|
95 | pub fn get_node_rev( | |
|
96 | &self, | |||
|
97 | node: NodePrefixRef, | |||
|
98 | ) -> Result<Revision, RevlogError> { | |||
96 | // This is brute force. But it is fast enough for now. |
|
99 | // This is brute force. But it is fast enough for now. | |
97 | // Optimization will come later. |
|
100 | // Optimization will come later. | |
98 | let mut found_by_prefix = None; |
|
101 | let mut found_by_prefix = None; | |
99 | for rev in (0..self.len() as Revision).rev() { |
|
102 | for rev in (0..self.len() as Revision).rev() { | |
100 | let index_entry = |
|
103 | let index_entry = | |
101 | self.index.get_entry(rev).ok_or(RevlogError::Corrupted)?; |
|
104 | self.index.get_entry(rev).ok_or(RevlogError::Corrupted)?; | |
102 |
if index_entry.hash() |
|
105 | if node == *index_entry.hash() { | |
103 | return Ok(rev); |
|
106 | return Ok(rev); | |
104 | } |
|
107 | } | |
105 |
if index_entry.hash() |
|
108 | if node.is_prefix_of(index_entry.hash()) { | |
106 | if found_by_prefix.is_some() { |
|
109 | if found_by_prefix.is_some() { | |
107 | return Err(RevlogError::AmbiguousPrefix); |
|
110 | return Err(RevlogError::AmbiguousPrefix); | |
108 | } |
|
111 | } | |
109 | found_by_prefix = Some(rev) |
|
112 | found_by_prefix = Some(rev) | |
110 | } |
|
113 | } | |
111 | } |
|
114 | } | |
112 | found_by_prefix.ok_or(RevlogError::InvalidRevision) |
|
115 | found_by_prefix.ok_or(RevlogError::InvalidRevision) | |
113 | } |
|
116 | } | |
114 |
|
117 | |||
115 | /// Return the full data associated to a revision. |
|
118 | /// Return the full data associated to a revision. | |
116 | /// |
|
119 | /// | |
117 | /// All entries required to build the final data out of deltas will be |
|
120 | /// All entries required to build the final data out of deltas will be | |
118 | /// retrieved as needed, and the deltas will be applied to the inital |
|
121 | /// retrieved as needed, and the deltas will be applied to the inital | |
119 | /// snapshot to rebuild the final data. |
|
122 | /// snapshot to rebuild the final data. | |
120 | #[timed] |
|
123 | #[timed] | |
121 | pub fn get_rev_data(&self, rev: Revision) -> Result<Vec<u8>, RevlogError> { |
|
124 | pub fn get_rev_data(&self, rev: Revision) -> Result<Vec<u8>, RevlogError> { | |
122 | // Todo return -> Cow |
|
125 | // Todo return -> Cow | |
123 | let mut entry = self.get_entry(rev)?; |
|
126 | let mut entry = self.get_entry(rev)?; | |
124 | let mut delta_chain = vec![]; |
|
127 | let mut delta_chain = vec![]; | |
125 | while let Some(base_rev) = entry.base_rev { |
|
128 | while let Some(base_rev) = entry.base_rev { | |
126 | delta_chain.push(entry); |
|
129 | delta_chain.push(entry); | |
127 | entry = |
|
130 | entry = | |
128 | self.get_entry(base_rev).or(Err(RevlogError::Corrupted))?; |
|
131 | self.get_entry(base_rev).or(Err(RevlogError::Corrupted))?; | |
129 | } |
|
132 | } | |
130 |
|
133 | |||
131 | // TODO do not look twice in the index |
|
134 | // TODO do not look twice in the index | |
132 | let index_entry = self |
|
135 | let index_entry = self | |
133 | .index |
|
136 | .index | |
134 | .get_entry(rev) |
|
137 | .get_entry(rev) | |
135 | .ok_or(RevlogError::InvalidRevision)?; |
|
138 | .ok_or(RevlogError::InvalidRevision)?; | |
136 |
|
139 | |||
137 | let data: Vec<u8> = if delta_chain.is_empty() { |
|
140 | let data: Vec<u8> = if delta_chain.is_empty() { | |
138 | entry.data()?.into() |
|
141 | entry.data()?.into() | |
139 | } else { |
|
142 | } else { | |
140 | Revlog::build_data_from_deltas(entry, &delta_chain)? |
|
143 | Revlog::build_data_from_deltas(entry, &delta_chain)? | |
141 | }; |
|
144 | }; | |
142 |
|
145 | |||
143 | if self.check_hash( |
|
146 | if self.check_hash( | |
144 | index_entry.p1(), |
|
147 | index_entry.p1(), | |
145 | index_entry.p2(), |
|
148 | index_entry.p2(), | |
146 | index_entry.hash(), |
|
149 | index_entry.hash().as_bytes(), | |
147 | &data, |
|
150 | &data, | |
148 | ) { |
|
151 | ) { | |
149 | Ok(data) |
|
152 | Ok(data) | |
150 | } else { |
|
153 | } else { | |
151 | Err(RevlogError::Corrupted) |
|
154 | Err(RevlogError::Corrupted) | |
152 | } |
|
155 | } | |
153 | } |
|
156 | } | |
154 |
|
157 | |||
155 | /// Check the hash of some given data against the recorded hash. |
|
158 | /// Check the hash of some given data against the recorded hash. | |
156 | pub fn check_hash( |
|
159 | pub fn check_hash( | |
157 | &self, |
|
160 | &self, | |
158 | p1: Revision, |
|
161 | p1: Revision, | |
159 | p2: Revision, |
|
162 | p2: Revision, | |
160 | expected: &[u8], |
|
163 | expected: &[u8], | |
161 | data: &[u8], |
|
164 | data: &[u8], | |
162 | ) -> bool { |
|
165 | ) -> bool { | |
163 | let e1 = self.index.get_entry(p1); |
|
166 | let e1 = self.index.get_entry(p1); | |
164 | let h1 = match e1 { |
|
167 | let h1 = match e1 { | |
165 | Some(ref entry) => entry.hash(), |
|
168 | Some(ref entry) => entry.hash(), | |
166 |
None => &NULL_NODE |
|
169 | None => &NULL_NODE, | |
167 | }; |
|
170 | }; | |
168 | let e2 = self.index.get_entry(p2); |
|
171 | let e2 = self.index.get_entry(p2); | |
169 | let h2 = match e2 { |
|
172 | let h2 = match e2 { | |
170 | Some(ref entry) => entry.hash(), |
|
173 | Some(ref entry) => entry.hash(), | |
171 |
None => &NULL_NODE |
|
174 | None => &NULL_NODE, | |
172 | }; |
|
175 | }; | |
173 |
|
176 | |||
174 |
hash(data, |
|
177 | hash(data, h1.as_bytes(), h2.as_bytes()).as_slice() == expected | |
175 | } |
|
178 | } | |
176 |
|
179 | |||
177 | /// Build the full data of a revision out its snapshot |
|
180 | /// Build the full data of a revision out its snapshot | |
178 | /// and its deltas. |
|
181 | /// and its deltas. | |
179 | #[timed] |
|
182 | #[timed] | |
180 | fn build_data_from_deltas( |
|
183 | fn build_data_from_deltas( | |
181 | snapshot: RevlogEntry, |
|
184 | snapshot: RevlogEntry, | |
182 | deltas: &[RevlogEntry], |
|
185 | deltas: &[RevlogEntry], | |
183 | ) -> Result<Vec<u8>, RevlogError> { |
|
186 | ) -> Result<Vec<u8>, RevlogError> { | |
184 | let snapshot = snapshot.data()?; |
|
187 | let snapshot = snapshot.data()?; | |
185 | let deltas = deltas |
|
188 | let deltas = deltas | |
186 | .iter() |
|
189 | .iter() | |
187 | .rev() |
|
190 | .rev() | |
188 | .map(RevlogEntry::data) |
|
191 | .map(RevlogEntry::data) | |
189 | .collect::<Result<Vec<Cow<'_, [u8]>>, RevlogError>>()?; |
|
192 | .collect::<Result<Vec<Cow<'_, [u8]>>, RevlogError>>()?; | |
190 | let patches: Vec<_> = |
|
193 | let patches: Vec<_> = | |
191 | deltas.iter().map(|d| patch::PatchList::new(d)).collect(); |
|
194 | deltas.iter().map(|d| patch::PatchList::new(d)).collect(); | |
192 | let patch = patch::fold_patch_lists(&patches); |
|
195 | let patch = patch::fold_patch_lists(&patches); | |
193 | Ok(patch.apply(&snapshot)) |
|
196 | Ok(patch.apply(&snapshot)) | |
194 | } |
|
197 | } | |
195 |
|
198 | |||
196 | /// Return the revlog data. |
|
199 | /// Return the revlog data. | |
197 | fn data(&self) -> &[u8] { |
|
200 | fn data(&self) -> &[u8] { | |
198 | match self.data_bytes { |
|
201 | match self.data_bytes { | |
199 | Some(ref data_bytes) => &data_bytes, |
|
202 | Some(ref data_bytes) => &data_bytes, | |
200 | None => panic!( |
|
203 | None => panic!( | |
201 | "forgot to load the data or trying to access inline data" |
|
204 | "forgot to load the data or trying to access inline data" | |
202 | ), |
|
205 | ), | |
203 | } |
|
206 | } | |
204 | } |
|
207 | } | |
205 |
|
208 | |||
206 | /// Get an entry of the revlog. |
|
209 | /// Get an entry of the revlog. | |
207 | fn get_entry(&self, rev: Revision) -> Result<RevlogEntry, RevlogError> { |
|
210 | fn get_entry(&self, rev: Revision) -> Result<RevlogEntry, RevlogError> { | |
208 | let index_entry = self |
|
211 | let index_entry = self | |
209 | .index |
|
212 | .index | |
210 | .get_entry(rev) |
|
213 | .get_entry(rev) | |
211 | .ok_or(RevlogError::InvalidRevision)?; |
|
214 | .ok_or(RevlogError::InvalidRevision)?; | |
212 | let start = index_entry.offset(); |
|
215 | let start = index_entry.offset(); | |
213 | let end = start + index_entry.compressed_len(); |
|
216 | let end = start + index_entry.compressed_len(); | |
214 | let data = if self.index.is_inline() { |
|
217 | let data = if self.index.is_inline() { | |
215 | self.index.data(start, end) |
|
218 | self.index.data(start, end) | |
216 | } else { |
|
219 | } else { | |
217 | &self.data()[start..end] |
|
220 | &self.data()[start..end] | |
218 | }; |
|
221 | }; | |
219 | let entry = RevlogEntry { |
|
222 | let entry = RevlogEntry { | |
220 | rev, |
|
223 | rev, | |
221 | bytes: data, |
|
224 | bytes: data, | |
222 | compressed_len: index_entry.compressed_len(), |
|
225 | compressed_len: index_entry.compressed_len(), | |
223 | uncompressed_len: index_entry.uncompressed_len(), |
|
226 | uncompressed_len: index_entry.uncompressed_len(), | |
224 | base_rev: if index_entry.base_revision() == rev { |
|
227 | base_rev: if index_entry.base_revision() == rev { | |
225 | None |
|
228 | None | |
226 | } else { |
|
229 | } else { | |
227 | Some(index_entry.base_revision()) |
|
230 | Some(index_entry.base_revision()) | |
228 | }, |
|
231 | }, | |
229 | }; |
|
232 | }; | |
230 | Ok(entry) |
|
233 | Ok(entry) | |
231 | } |
|
234 | } | |
232 | } |
|
235 | } | |
233 |
|
236 | |||
234 | /// The revlog entry's bytes and the necessary informations to extract |
|
237 | /// The revlog entry's bytes and the necessary informations to extract | |
235 | /// the entry's data. |
|
238 | /// the entry's data. | |
236 | #[derive(Debug)] |
|
239 | #[derive(Debug)] | |
237 | pub struct RevlogEntry<'a> { |
|
240 | pub struct RevlogEntry<'a> { | |
238 | rev: Revision, |
|
241 | rev: Revision, | |
239 | bytes: &'a [u8], |
|
242 | bytes: &'a [u8], | |
240 | compressed_len: usize, |
|
243 | compressed_len: usize, | |
241 | uncompressed_len: usize, |
|
244 | uncompressed_len: usize, | |
242 | base_rev: Option<Revision>, |
|
245 | base_rev: Option<Revision>, | |
243 | } |
|
246 | } | |
244 |
|
247 | |||
245 | impl<'a> RevlogEntry<'a> { |
|
248 | impl<'a> RevlogEntry<'a> { | |
246 | /// Extract the data contained in the entry. |
|
249 | /// Extract the data contained in the entry. | |
247 | pub fn data(&self) -> Result<Cow<'_, [u8]>, RevlogError> { |
|
250 | pub fn data(&self) -> Result<Cow<'_, [u8]>, RevlogError> { | |
248 | if self.bytes.is_empty() { |
|
251 | if self.bytes.is_empty() { | |
249 | return Ok(Cow::Borrowed(&[])); |
|
252 | return Ok(Cow::Borrowed(&[])); | |
250 | } |
|
253 | } | |
251 | match self.bytes[0] { |
|
254 | match self.bytes[0] { | |
252 | // Revision data is the entirety of the entry, including this |
|
255 | // Revision data is the entirety of the entry, including this | |
253 | // header. |
|
256 | // header. | |
254 | b'\0' => Ok(Cow::Borrowed(self.bytes)), |
|
257 | b'\0' => Ok(Cow::Borrowed(self.bytes)), | |
255 | // Raw revision data follows. |
|
258 | // Raw revision data follows. | |
256 | b'u' => Ok(Cow::Borrowed(&self.bytes[1..])), |
|
259 | b'u' => Ok(Cow::Borrowed(&self.bytes[1..])), | |
257 | // zlib (RFC 1950) data. |
|
260 | // zlib (RFC 1950) data. | |
258 | b'x' => Ok(Cow::Owned(self.uncompressed_zlib_data()?)), |
|
261 | b'x' => Ok(Cow::Owned(self.uncompressed_zlib_data()?)), | |
259 | // zstd data. |
|
262 | // zstd data. | |
260 | b'\x28' => Ok(Cow::Owned(self.uncompressed_zstd_data()?)), |
|
263 | b'\x28' => Ok(Cow::Owned(self.uncompressed_zstd_data()?)), | |
261 | format_type => Err(RevlogError::UnknowDataFormat(format_type)), |
|
264 | format_type => Err(RevlogError::UnknowDataFormat(format_type)), | |
262 | } |
|
265 | } | |
263 | } |
|
266 | } | |
264 |
|
267 | |||
265 | fn uncompressed_zlib_data(&self) -> Result<Vec<u8>, RevlogError> { |
|
268 | fn uncompressed_zlib_data(&self) -> Result<Vec<u8>, RevlogError> { | |
266 | let mut decoder = ZlibDecoder::new(self.bytes); |
|
269 | let mut decoder = ZlibDecoder::new(self.bytes); | |
267 | if self.is_delta() { |
|
270 | if self.is_delta() { | |
268 | let mut buf = Vec::with_capacity(self.compressed_len); |
|
271 | let mut buf = Vec::with_capacity(self.compressed_len); | |
269 | decoder |
|
272 | decoder | |
270 | .read_to_end(&mut buf) |
|
273 | .read_to_end(&mut buf) | |
271 | .or(Err(RevlogError::Corrupted))?; |
|
274 | .or(Err(RevlogError::Corrupted))?; | |
272 | Ok(buf) |
|
275 | Ok(buf) | |
273 | } else { |
|
276 | } else { | |
274 | let mut buf = vec![0; self.uncompressed_len]; |
|
277 | let mut buf = vec![0; self.uncompressed_len]; | |
275 | decoder |
|
278 | decoder | |
276 | .read_exact(&mut buf) |
|
279 | .read_exact(&mut buf) | |
277 | .or(Err(RevlogError::Corrupted))?; |
|
280 | .or(Err(RevlogError::Corrupted))?; | |
278 | Ok(buf) |
|
281 | Ok(buf) | |
279 | } |
|
282 | } | |
280 | } |
|
283 | } | |
281 |
|
284 | |||
282 | fn uncompressed_zstd_data(&self) -> Result<Vec<u8>, RevlogError> { |
|
285 | fn uncompressed_zstd_data(&self) -> Result<Vec<u8>, RevlogError> { | |
283 | if self.is_delta() { |
|
286 | if self.is_delta() { | |
284 | let mut buf = Vec::with_capacity(self.compressed_len); |
|
287 | let mut buf = Vec::with_capacity(self.compressed_len); | |
285 | zstd::stream::copy_decode(self.bytes, &mut buf) |
|
288 | zstd::stream::copy_decode(self.bytes, &mut buf) | |
286 | .or(Err(RevlogError::Corrupted))?; |
|
289 | .or(Err(RevlogError::Corrupted))?; | |
287 | Ok(buf) |
|
290 | Ok(buf) | |
288 | } else { |
|
291 | } else { | |
289 | let mut buf = vec![0; self.uncompressed_len]; |
|
292 | let mut buf = vec![0; self.uncompressed_len]; | |
290 | let len = zstd::block::decompress_to_buffer(self.bytes, &mut buf) |
|
293 | let len = zstd::block::decompress_to_buffer(self.bytes, &mut buf) | |
291 | .or(Err(RevlogError::Corrupted))?; |
|
294 | .or(Err(RevlogError::Corrupted))?; | |
292 | if len != self.uncompressed_len { |
|
295 | if len != self.uncompressed_len { | |
293 | Err(RevlogError::Corrupted) |
|
296 | Err(RevlogError::Corrupted) | |
294 | } else { |
|
297 | } else { | |
295 | Ok(buf) |
|
298 | Ok(buf) | |
296 | } |
|
299 | } | |
297 | } |
|
300 | } | |
298 | } |
|
301 | } | |
299 |
|
302 | |||
300 | /// Tell if the entry is a snapshot or a delta |
|
303 | /// Tell if the entry is a snapshot or a delta | |
301 | /// (influences on decompression). |
|
304 | /// (influences on decompression). | |
302 | fn is_delta(&self) -> bool { |
|
305 | fn is_delta(&self) -> bool { | |
303 | self.base_rev.is_some() |
|
306 | self.base_rev.is_some() | |
304 | } |
|
307 | } | |
305 | } |
|
308 | } | |
306 |
|
309 | |||
307 | /// Format version of the revlog. |
|
310 | /// Format version of the revlog. | |
308 | pub fn get_version(index_bytes: &[u8]) -> u16 { |
|
311 | pub fn get_version(index_bytes: &[u8]) -> u16 { | |
309 | BigEndian::read_u16(&index_bytes[2..=3]) |
|
312 | BigEndian::read_u16(&index_bytes[2..=3]) | |
310 | } |
|
313 | } | |
311 |
|
314 | |||
312 | /// Calculate the hash of a revision given its data and its parents. |
|
315 | /// Calculate the hash of a revision given its data and its parents. | |
313 | fn hash(data: &[u8], p1_hash: &[u8], p2_hash: &[u8]) -> Vec<u8> { |
|
316 | fn hash(data: &[u8], p1_hash: &[u8], p2_hash: &[u8]) -> Vec<u8> { | |
314 | let mut hasher = Sha1::new(); |
|
317 | let mut hasher = Sha1::new(); | |
315 | let (a, b) = (p1_hash, p2_hash); |
|
318 | let (a, b) = (p1_hash, p2_hash); | |
316 | if a > b { |
|
319 | if a > b { | |
317 | hasher.input(b); |
|
320 | hasher.input(b); | |
318 | hasher.input(a); |
|
321 | hasher.input(a); | |
319 | } else { |
|
322 | } else { | |
320 | hasher.input(a); |
|
323 | hasher.input(a); | |
321 | hasher.input(b); |
|
324 | hasher.input(b); | |
322 | } |
|
325 | } | |
323 | hasher.input(data); |
|
326 | hasher.input(data); | |
324 | let mut hash = vec![0; NODE_BYTES_LENGTH]; |
|
327 | let mut hash = vec![0; NODE_BYTES_LENGTH]; | |
325 | hasher.result(&mut hash); |
|
328 | hasher.result(&mut hash); | |
326 | hash |
|
329 | hash | |
327 | } |
|
330 | } | |
328 |
|
331 | |||
329 | #[cfg(test)] |
|
332 | #[cfg(test)] | |
330 | mod tests { |
|
333 | mod tests { | |
331 | use super::*; |
|
334 | use super::*; | |
332 |
|
335 | |||
333 | use super::super::index::IndexEntryBuilder; |
|
336 | use super::super::index::IndexEntryBuilder; | |
334 |
|
337 | |||
335 | #[test] |
|
338 | #[test] | |
336 | fn version_test() { |
|
339 | fn version_test() { | |
337 | let bytes = IndexEntryBuilder::new() |
|
340 | let bytes = IndexEntryBuilder::new() | |
338 | .is_first(true) |
|
341 | .is_first(true) | |
339 | .with_version(1) |
|
342 | .with_version(1) | |
340 | .build(); |
|
343 | .build(); | |
341 |
|
344 | |||
342 | assert_eq!(get_version(&bytes), 1) |
|
345 | assert_eq!(get_version(&bytes), 1) | |
343 | } |
|
346 | } | |
344 | } |
|
347 | } |
@@ -1,165 +1,167 b'' | |||||
1 | #require rust |
|
1 | #require rust | |
2 |
|
2 | |||
3 | Define an rhg function that will only run if rhg exists |
|
3 | Define an rhg function that will only run if rhg exists | |
4 | $ rhg() { |
|
4 | $ rhg() { | |
5 | > if [ -f "$RUNTESTDIR/../rust/target/debug/rhg" ]; then |
|
5 | > if [ -f "$RUNTESTDIR/../rust/target/debug/rhg" ]; then | |
6 | > "$RUNTESTDIR/../rust/target/debug/rhg" "$@" |
|
6 | > "$RUNTESTDIR/../rust/target/debug/rhg" "$@" | |
7 | > else |
|
7 | > else | |
8 | > echo "skipped: Cannot find rhg. Try to run cargo build in rust/rhg." |
|
8 | > echo "skipped: Cannot find rhg. Try to run cargo build in rust/rhg." | |
9 | > exit 80 |
|
9 | > exit 80 | |
10 | > fi |
|
10 | > fi | |
11 | > } |
|
11 | > } | |
12 |
|
12 | |||
13 | Unimplemented command |
|
13 | Unimplemented command | |
14 | $ rhg unimplemented-command |
|
14 | $ rhg unimplemented-command | |
15 | error: Found argument 'unimplemented-command' which wasn't expected, or isn't valid in this context |
|
15 | error: Found argument 'unimplemented-command' which wasn't expected, or isn't valid in this context | |
16 |
|
16 | |||
17 | USAGE: |
|
17 | USAGE: | |
18 | rhg <SUBCOMMAND> |
|
18 | rhg <SUBCOMMAND> | |
19 |
|
19 | |||
20 | For more information try --help |
|
20 | For more information try --help | |
21 | [252] |
|
21 | [252] | |
22 |
|
22 | |||
23 | Finding root |
|
23 | Finding root | |
24 | $ rhg root |
|
24 | $ rhg root | |
25 | abort: no repository found in '$TESTTMP' (.hg not found)! |
|
25 | abort: no repository found in '$TESTTMP' (.hg not found)! | |
26 | [255] |
|
26 | [255] | |
27 |
|
27 | |||
28 | $ hg init repository |
|
28 | $ hg init repository | |
29 | $ cd repository |
|
29 | $ cd repository | |
30 | $ rhg root |
|
30 | $ rhg root | |
31 | $TESTTMP/repository |
|
31 | $TESTTMP/repository | |
32 |
|
32 | |||
33 | Unwritable file descriptor |
|
33 | Unwritable file descriptor | |
34 | $ rhg root > /dev/full |
|
34 | $ rhg root > /dev/full | |
35 | abort: No space left on device (os error 28) |
|
35 | abort: No space left on device (os error 28) | |
36 | [255] |
|
36 | [255] | |
37 |
|
37 | |||
38 | Deleted repository |
|
38 | Deleted repository | |
39 | $ rm -rf `pwd` |
|
39 | $ rm -rf `pwd` | |
40 | $ rhg root |
|
40 | $ rhg root | |
41 | abort: error getting current working directory: $ENOENT$ |
|
41 | abort: error getting current working directory: $ENOENT$ | |
42 | [255] |
|
42 | [255] | |
43 |
|
43 | |||
44 | Listing tracked files |
|
44 | Listing tracked files | |
45 | $ cd $TESTTMP |
|
45 | $ cd $TESTTMP | |
46 | $ hg init repository |
|
46 | $ hg init repository | |
47 | $ cd repository |
|
47 | $ cd repository | |
48 | $ for i in 1 2 3; do |
|
48 | $ for i in 1 2 3; do | |
49 | > echo $i >> file$i |
|
49 | > echo $i >> file$i | |
50 | > hg add file$i |
|
50 | > hg add file$i | |
51 | > done |
|
51 | > done | |
52 | > hg commit -m "commit $i" -q |
|
52 | > hg commit -m "commit $i" -q | |
53 |
|
53 | |||
54 | Listing tracked files from root |
|
54 | Listing tracked files from root | |
55 | $ rhg files |
|
55 | $ rhg files | |
56 | file1 |
|
56 | file1 | |
57 | file2 |
|
57 | file2 | |
58 | file3 |
|
58 | file3 | |
59 |
|
59 | |||
60 | Listing tracked files from subdirectory |
|
60 | Listing tracked files from subdirectory | |
61 | $ mkdir -p path/to/directory |
|
61 | $ mkdir -p path/to/directory | |
62 | $ cd path/to/directory |
|
62 | $ cd path/to/directory | |
63 | $ rhg files |
|
63 | $ rhg files | |
64 | ../../../file1 |
|
64 | ../../../file1 | |
65 | ../../../file2 |
|
65 | ../../../file2 | |
66 | ../../../file3 |
|
66 | ../../../file3 | |
67 |
|
67 | |||
68 | Listing tracked files through broken pipe |
|
68 | Listing tracked files through broken pipe | |
69 | $ rhg files | head -n 1 |
|
69 | $ rhg files | head -n 1 | |
70 | ../../../file1 |
|
70 | ../../../file1 | |
71 |
|
71 | |||
72 | Debuging data in inline index |
|
72 | Debuging data in inline index | |
73 | $ cd $TESTTMP |
|
73 | $ cd $TESTTMP | |
74 | $ rm -rf repository |
|
74 | $ rm -rf repository | |
75 | $ hg init repository |
|
75 | $ hg init repository | |
76 | $ cd repository |
|
76 | $ cd repository | |
77 | $ for i in 1 2 3 4 5 6; do |
|
77 | $ for i in 1 2 3 4 5 6; do | |
78 | > echo $i >> file-$i |
|
78 | > echo $i >> file-$i | |
79 | > hg add file-$i |
|
79 | > hg add file-$i | |
80 | > hg commit -m "Commit $i" -q |
|
80 | > hg commit -m "Commit $i" -q | |
81 | > done |
|
81 | > done | |
82 | $ rhg debugdata -c 2 |
|
82 | $ rhg debugdata -c 2 | |
83 | 8d0267cb034247ebfa5ee58ce59e22e57a492297 |
|
83 | 8d0267cb034247ebfa5ee58ce59e22e57a492297 | |
84 | test |
|
84 | test | |
85 | 0 0 |
|
85 | 0 0 | |
86 | file-3 |
|
86 | file-3 | |
87 |
|
87 | |||
88 | Commit 3 (no-eol) |
|
88 | Commit 3 (no-eol) | |
89 | $ rhg debugdata -m 2 |
|
89 | $ rhg debugdata -m 2 | |
90 | file-1\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc) |
|
90 | file-1\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc) | |
91 | file-2\x005d9299349fc01ddd25d0070d149b124d8f10411e (esc) |
|
91 | file-2\x005d9299349fc01ddd25d0070d149b124d8f10411e (esc) | |
92 | file-3\x002661d26c649684b482d10f91960cc3db683c38b4 (esc) |
|
92 | file-3\x002661d26c649684b482d10f91960cc3db683c38b4 (esc) | |
93 |
|
93 | |||
94 | Debuging with full node id |
|
94 | Debuging with full node id | |
95 | $ rhg debugdata -c `hg log -r 0 -T '{node}'` |
|
95 | $ rhg debugdata -c `hg log -r 0 -T '{node}'` | |
96 | d1d1c679d3053e8926061b6f45ca52009f011e3f |
|
96 | d1d1c679d3053e8926061b6f45ca52009f011e3f | |
97 | test |
|
97 | test | |
98 | 0 0 |
|
98 | 0 0 | |
99 | file-1 |
|
99 | file-1 | |
100 |
|
100 | |||
101 | Commit 1 (no-eol) |
|
101 | Commit 1 (no-eol) | |
102 |
|
102 | |||
103 | Specifying revisions by changeset ID |
|
103 | Specifying revisions by changeset ID | |
104 | $ hg log -T '{node}\n' |
|
104 | $ hg log -T '{node}\n' | |
105 | c6ad58c44207b6ff8a4fbbca7045a5edaa7e908b |
|
105 | c6ad58c44207b6ff8a4fbbca7045a5edaa7e908b | |
106 | d654274993d0149eecc3cc03214f598320211900 |
|
106 | d654274993d0149eecc3cc03214f598320211900 | |
107 | f646af7e96481d3a5470b695cf30ad8e3ab6c575 |
|
107 | f646af7e96481d3a5470b695cf30ad8e3ab6c575 | |
108 | cf8b83f14ead62b374b6e91a0e9303b85dfd9ed7 |
|
108 | cf8b83f14ead62b374b6e91a0e9303b85dfd9ed7 | |
109 | 91c6f6e73e39318534dc415ea4e8a09c99cd74d6 |
|
109 | 91c6f6e73e39318534dc415ea4e8a09c99cd74d6 | |
110 | 6ae9681c6d30389694d8701faf24b583cf3ccafe |
|
110 | 6ae9681c6d30389694d8701faf24b583cf3ccafe | |
111 | $ rhg files -r cf8b83 |
|
111 | $ rhg files -r cf8b83 | |
112 | file-1 |
|
112 | file-1 | |
113 | file-2 |
|
113 | file-2 | |
114 | file-3 |
|
114 | file-3 | |
115 | $ rhg cat -r cf8b83 file-2 |
|
115 | $ rhg cat -r cf8b83 file-2 | |
116 | 2 |
|
116 | 2 | |
117 | $ rhg cat -r c file-2 |
|
117 | $ rhg cat -r c file-2 | |
118 |
abort: |
|
118 | abort: ambiguous revision identifier c | |
119 | [255] |
|
119 | [255] | |
|
120 | $ rhg cat -r d file-2 | |||
|
121 | 2 | |||
120 |
|
122 | |||
121 | Cat files |
|
123 | Cat files | |
122 | $ cd $TESTTMP |
|
124 | $ cd $TESTTMP | |
123 | $ rm -rf repository |
|
125 | $ rm -rf repository | |
124 | $ hg init repository |
|
126 | $ hg init repository | |
125 | $ cd repository |
|
127 | $ cd repository | |
126 | $ echo "original content" > original |
|
128 | $ echo "original content" > original | |
127 | $ hg add original |
|
129 | $ hg add original | |
128 | $ hg commit -m "add original" original |
|
130 | $ hg commit -m "add original" original | |
129 | $ rhg cat -r 0 original |
|
131 | $ rhg cat -r 0 original | |
130 | original content |
|
132 | original content | |
131 | Cat copied file should not display copy metadata |
|
133 | Cat copied file should not display copy metadata | |
132 | $ hg copy original copy_of_original |
|
134 | $ hg copy original copy_of_original | |
133 | $ hg commit -m "add copy of original" |
|
135 | $ hg commit -m "add copy of original" | |
134 | $ rhg cat -r 1 copy_of_original |
|
136 | $ rhg cat -r 1 copy_of_original | |
135 | original content |
|
137 | original content | |
136 |
|
138 | |||
137 | Requirements |
|
139 | Requirements | |
138 | $ rhg debugrequirements |
|
140 | $ rhg debugrequirements | |
139 | dotencode |
|
141 | dotencode | |
140 | fncache |
|
142 | fncache | |
141 | generaldelta |
|
143 | generaldelta | |
142 | revlogv1 |
|
144 | revlogv1 | |
143 | sparserevlog |
|
145 | sparserevlog | |
144 | store |
|
146 | store | |
145 |
|
147 | |||
146 | $ echo indoor-pool >> .hg/requires |
|
148 | $ echo indoor-pool >> .hg/requires | |
147 | $ rhg files |
|
149 | $ rhg files | |
148 | [252] |
|
150 | [252] | |
149 |
|
151 | |||
150 | $ rhg cat -r 1 copy_of_original |
|
152 | $ rhg cat -r 1 copy_of_original | |
151 | [252] |
|
153 | [252] | |
152 |
|
154 | |||
153 | $ rhg debugrequirements |
|
155 | $ rhg debugrequirements | |
154 | dotencode |
|
156 | dotencode | |
155 | fncache |
|
157 | fncache | |
156 | generaldelta |
|
158 | generaldelta | |
157 | revlogv1 |
|
159 | revlogv1 | |
158 | sparserevlog |
|
160 | sparserevlog | |
159 | store |
|
161 | store | |
160 | indoor-pool |
|
162 | indoor-pool | |
161 |
|
163 | |||
162 | $ echo -e '\xFF' >> .hg/requires |
|
164 | $ echo -e '\xFF' >> .hg/requires | |
163 | $ rhg debugrequirements |
|
165 | $ rhg debugrequirements | |
164 | abort: .hg/requires is corrupted |
|
166 | abort: .hg/requires is corrupted | |
165 | [255] |
|
167 | [255] |
General Comments 0
You need to be logged in to leave comments.
Login now