Mercurial > hg
comparison rust/hg-core/src/revlog/index.rs @ 45526:26c53ee51c68
hg-core: Add a limited read only `revlog` implementation
Only covers the needs of the upcoming `rhg debugdata` command.
Differential Revision: https://phab.mercurial-scm.org/D8958
author | Antoine Cezar <antoine.cezar@octobus.net> |
---|---|
date | Fri, 04 Sep 2020 11:55:07 +0200 |
parents | |
children | b0d6309ff50c |
comparison
equal
deleted
inserted
replaced
45525:590a840fa367 | 45526:26c53ee51c68 |
---|---|
1 use crate::revlog::{Revision, NULL_REVISION}; | |
2 use byteorder::{BigEndian, ByteOrder}; | |
3 | |
4 pub const INDEX_ENTRY_SIZE: usize = 64; | |
5 | |
6 /// A Revlog index | |
7 #[derive(Debug)] | |
8 pub struct Index<'a> { | |
9 bytes: &'a [u8], | |
10 /// Offsets of starts of index blocks. | |
11 /// Only needed when the index is interleaved with data. | |
12 offsets: Option<Vec<usize>>, | |
13 } | |
14 | |
15 impl<'a> Index<'a> { | |
16 /// Create an index from bytes. | |
17 /// Calculate the start of each entry when is_inline is true. | |
18 pub fn new(bytes: &'a [u8], is_inline: bool) -> Self { | |
19 if is_inline { | |
20 let mut offset: usize = 0; | |
21 let mut offsets = Vec::new(); | |
22 | |
23 while (bytes.len() - offset) >= INDEX_ENTRY_SIZE { | |
24 offsets.push(offset); | |
25 let end = offset + INDEX_ENTRY_SIZE; | |
26 let entry = IndexEntry { | |
27 bytes: &bytes[offset..end], | |
28 offset_override: None, | |
29 }; | |
30 | |
31 offset += INDEX_ENTRY_SIZE + entry.compressed_len(); | |
32 } | |
33 | |
34 Self { | |
35 bytes, | |
36 offsets: Some(offsets), | |
37 } | |
38 } else { | |
39 Self { | |
40 bytes, | |
41 offsets: None, | |
42 } | |
43 } | |
44 } | |
45 | |
46 /// Return the index entry corresponding to the given revision if it | |
47 /// exists. | |
48 pub fn get_entry(&self, rev: Revision) -> Option<IndexEntry> { | |
49 if rev == NULL_REVISION { | |
50 return None; | |
51 } | |
52 if let Some(offsets) = &self.offsets { | |
53 self.get_entry_inline(rev, offsets) | |
54 } else { | |
55 self.get_entry_separated(rev) | |
56 } | |
57 } | |
58 | |
59 fn get_entry_inline( | |
60 &self, | |
61 rev: Revision, | |
62 offsets: &[usize], | |
63 ) -> Option<IndexEntry> { | |
64 let start = *offsets.get(rev as usize)?; | |
65 let end = start.checked_add(INDEX_ENTRY_SIZE)?; | |
66 let bytes = &self.bytes[start..end]; | |
67 | |
68 // See IndexEntry for an explanation of this override. | |
69 let offset_override = Some(end); | |
70 | |
71 Some(IndexEntry { | |
72 bytes, | |
73 offset_override, | |
74 }) | |
75 } | |
76 | |
77 fn get_entry_separated(&self, rev: Revision) -> Option<IndexEntry> { | |
78 let max_rev = self.bytes.len() / INDEX_ENTRY_SIZE; | |
79 if rev as usize >= max_rev { | |
80 return None; | |
81 } | |
82 let start = rev as usize * INDEX_ENTRY_SIZE; | |
83 let end = start + INDEX_ENTRY_SIZE; | |
84 let bytes = &self.bytes[start..end]; | |
85 | |
86 // See IndexEntry for an explanation of this override. | |
87 let offset_override = match rev { | |
88 0 => Some(0), | |
89 _ => None, | |
90 }; | |
91 | |
92 Some(IndexEntry { | |
93 bytes, | |
94 offset_override, | |
95 }) | |
96 } | |
97 } | |
98 | |
99 #[derive(Debug)] | |
100 pub struct IndexEntry<'a> { | |
101 bytes: &'a [u8], | |
102 /// Allows to override the offset value of the entry. | |
103 /// | |
104 /// For interleaved index and data, the offset stored in the index | |
105 /// corresponds to the separated data offset. | |
106 /// It has to be overridden with the actual offset in the interleaved | |
107 /// index which is just after the index block. | |
108 /// | |
109 /// For separated index and data, the offset stored in the first index | |
110 /// entry is mixed with the index headers. | |
111 /// It has to be overridden with 0. | |
112 offset_override: Option<usize>, | |
113 } | |
114 | |
115 impl<'a> IndexEntry<'a> { | |
116 /// Return the offset of the data if not overridden by offset_override. | |
117 pub fn offset(&self) -> usize { | |
118 if let Some(offset_override) = self.offset_override { | |
119 offset_override | |
120 } else { | |
121 let mut bytes = [0; 8]; | |
122 bytes[2..8].copy_from_slice(&self.bytes[0..=5]); | |
123 BigEndian::read_u64(&bytes[..]) as usize | |
124 } | |
125 } | |
126 | |
127 /// Return the compressed length of the data. | |
128 pub fn compressed_len(&self) -> usize { | |
129 BigEndian::read_u32(&self.bytes[8..=11]) as usize | |
130 } | |
131 | |
132 /// Return the uncompressed length of the data. | |
133 pub fn uncompressed_len(&self) -> usize { | |
134 BigEndian::read_u32(&self.bytes[12..=15]) as usize | |
135 } | |
136 | |
137 /// Return the revision upon which the data has been derived. | |
138 pub fn base_revision(&self) -> Revision { | |
139 // TODO Maybe return an Option when base_revision == rev? | |
140 // Requires to add rev to IndexEntry | |
141 | |
142 BigEndian::read_i32(&self.bytes[16..]) | |
143 } | |
144 } | |
145 | |
146 #[cfg(test)] | |
147 mod tests { | |
148 use super::*; | |
149 | |
150 #[cfg(test)] | |
151 #[derive(Debug, Copy, Clone)] | |
152 pub struct IndexEntryBuilder { | |
153 is_first: bool, | |
154 is_inline: bool, | |
155 is_general_delta: bool, | |
156 version: u16, | |
157 offset: usize, | |
158 compressed_len: usize, | |
159 uncompressed_len: usize, | |
160 base_revision: Revision, | |
161 } | |
162 | |
163 #[cfg(test)] | |
164 impl IndexEntryBuilder { | |
165 pub fn new() -> Self { | |
166 Self { | |
167 is_first: false, | |
168 is_inline: false, | |
169 is_general_delta: true, | |
170 version: 2, | |
171 offset: 0, | |
172 compressed_len: 0, | |
173 uncompressed_len: 0, | |
174 base_revision: 0, | |
175 } | |
176 } | |
177 | |
178 pub fn is_first(&mut self, value: bool) -> &mut Self { | |
179 self.is_first = value; | |
180 self | |
181 } | |
182 | |
183 pub fn with_inline(&mut self, value: bool) -> &mut Self { | |
184 self.is_inline = value; | |
185 self | |
186 } | |
187 | |
188 pub fn with_general_delta(&mut self, value: bool) -> &mut Self { | |
189 self.is_general_delta = value; | |
190 self | |
191 } | |
192 | |
193 pub fn with_version(&mut self, value: u16) -> &mut Self { | |
194 self.version = value; | |
195 self | |
196 } | |
197 | |
198 pub fn with_offset(&mut self, value: usize) -> &mut Self { | |
199 self.offset = value; | |
200 self | |
201 } | |
202 | |
203 pub fn with_compressed_len(&mut self, value: usize) -> &mut Self { | |
204 self.compressed_len = value; | |
205 self | |
206 } | |
207 | |
208 pub fn with_uncompressed_len(&mut self, value: usize) -> &mut Self { | |
209 self.uncompressed_len = value; | |
210 self | |
211 } | |
212 | |
213 pub fn with_base_revision(&mut self, value: Revision) -> &mut Self { | |
214 self.base_revision = value; | |
215 self | |
216 } | |
217 | |
218 pub fn build(&self) -> Vec<u8> { | |
219 let mut bytes = Vec::with_capacity(INDEX_ENTRY_SIZE); | |
220 if self.is_first { | |
221 bytes.extend(&match (self.is_general_delta, self.is_inline) { | |
222 (false, false) => [0u8, 0], | |
223 (false, true) => [0u8, 1], | |
224 (true, false) => [0u8, 2], | |
225 (true, true) => [0u8, 3], | |
226 }); | |
227 bytes.extend(&self.version.to_be_bytes()); | |
228 // Remaining offset bytes. | |
229 bytes.extend(&[0u8; 2]); | |
230 } else { | |
231 // Offset is only 6 bytes will usize is 8. | |
232 bytes.extend(&self.offset.to_be_bytes()[2..]); | |
233 } | |
234 bytes.extend(&[0u8; 2]); // Revision flags. | |
235 bytes.extend(&self.compressed_len.to_be_bytes()[4..]); | |
236 bytes.extend(&self.uncompressed_len.to_be_bytes()[4..]); | |
237 bytes.extend(&self.base_revision.to_be_bytes()); | |
238 bytes | |
239 } | |
240 } | |
241 | |
242 #[test] | |
243 fn test_offset() { | |
244 let bytes = IndexEntryBuilder::new().with_offset(1).build(); | |
245 let entry = IndexEntry { | |
246 bytes: &bytes, | |
247 offset_override: None, | |
248 }; | |
249 | |
250 assert_eq!(entry.offset(), 1) | |
251 } | |
252 | |
253 #[test] | |
254 fn test_with_overridden_offset() { | |
255 let bytes = IndexEntryBuilder::new().with_offset(1).build(); | |
256 let entry = IndexEntry { | |
257 bytes: &bytes, | |
258 offset_override: Some(2), | |
259 }; | |
260 | |
261 assert_eq!(entry.offset(), 2) | |
262 } | |
263 | |
264 #[test] | |
265 fn test_compressed_len() { | |
266 let bytes = IndexEntryBuilder::new().with_compressed_len(1).build(); | |
267 let entry = IndexEntry { | |
268 bytes: &bytes, | |
269 offset_override: None, | |
270 }; | |
271 | |
272 assert_eq!(entry.compressed_len(), 1) | |
273 } | |
274 | |
275 #[test] | |
276 fn test_uncompressed_len() { | |
277 let bytes = IndexEntryBuilder::new().with_uncompressed_len(1).build(); | |
278 let entry = IndexEntry { | |
279 bytes: &bytes, | |
280 offset_override: None, | |
281 }; | |
282 | |
283 assert_eq!(entry.uncompressed_len(), 1) | |
284 } | |
285 | |
286 #[test] | |
287 fn test_base_revision() { | |
288 let bytes = IndexEntryBuilder::new().with_base_revision(1).build(); | |
289 let entry = IndexEntry { | |
290 bytes: &bytes, | |
291 offset_override: None, | |
292 }; | |
293 | |
294 assert_eq!(entry.base_revision(), 1) | |
295 } | |
296 } | |
297 | |
298 #[cfg(test)] | |
299 pub use tests::IndexEntryBuilder; |