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;