##// END OF EJS Templates
hg-core: add filelog metadata header parsing...
Mitchell Kember -
r53293:b0b6c28b default
parent child Browse files
Show More
@@ -11,6 +11,7 use crate::utils::hg_path::HgPath;
11 use crate::utils::SliceExt;
11 use crate::utils::SliceExt;
12 use crate::Graph;
12 use crate::Graph;
13 use crate::GraphError;
13 use crate::GraphError;
14 use crate::Node;
14 use crate::UncheckedRevision;
15 use crate::UncheckedRevision;
15 use std::path::PathBuf;
16 use std::path::PathBuf;
16
17
@@ -216,22 +217,30 pub struct FilelogRevisionData(Vec<u8>);
216
217
217 impl FilelogRevisionData {
218 impl FilelogRevisionData {
218 /// Split into metadata and data
219 /// Split into metadata and data
219 pub fn split(&self) -> Result<(Option<&[u8]>, &[u8]), HgError> {
220 pub fn split(
221 &self,
222 ) -> Result<(FilelogRevisionMetadata<'_>, &[u8]), HgError> {
220 const DELIMITER: &[u8; 2] = b"\x01\n";
223 const DELIMITER: &[u8; 2] = b"\x01\n";
221
224
222 if let Some(rest) = self.0.drop_prefix(DELIMITER) {
225 if let Some(rest) = self.0.drop_prefix(DELIMITER) {
223 if let Some((metadata, data)) = rest.split_2_by_slice(DELIMITER) {
226 if let Some((metadata, data)) = rest.split_2_by_slice(DELIMITER) {
224 Ok((Some(metadata), data))
227 Ok((FilelogRevisionMetadata(Some(metadata)), data))
225 } else {
228 } else {
226 Err(HgError::corrupted(
229 Err(HgError::corrupted(
227 "Missing metadata end delimiter in filelog entry",
230 "Missing metadata end delimiter in filelog entry",
228 ))
231 ))
229 }
232 }
230 } else {
233 } else {
231 Ok((None, &self.0))
234 Ok((FilelogRevisionMetadata(None), &self.0))
232 }
235 }
233 }
236 }
234
237
238 /// Returns the metadata header.
239 pub fn metadata(&self) -> Result<FilelogRevisionMetadata<'_>, HgError> {
240 let (metadata, _data) = self.split()?;
241 Ok(metadata)
242 }
243
235 /// Returns the file contents at this revision, stripped of any metadata
244 /// Returns the file contents at this revision, stripped of any metadata
236 pub fn file_data(&self) -> Result<&[u8], HgError> {
245 pub fn file_data(&self) -> Result<&[u8], HgError> {
237 let (_metadata, data) = self.split()?;
246 let (_metadata, data) = self.split()?;
@@ -241,10 +250,138 impl FilelogRevisionData {
241 /// Consume the entry, and convert it into data, discarding any metadata,
250 /// Consume the entry, and convert it into data, discarding any metadata,
242 /// if present.
251 /// if present.
243 pub fn into_file_data(self) -> Result<Vec<u8>, HgError> {
252 pub fn into_file_data(self) -> Result<Vec<u8>, HgError> {
244 if let (Some(_metadata), data) = self.split()? {
253 if let (FilelogRevisionMetadata(Some(_)), data) = self.split()? {
245 Ok(data.to_owned())
254 Ok(data.to_owned())
246 } else {
255 } else {
247 Ok(self.0)
256 Ok(self.0)
248 }
257 }
249 }
258 }
250 }
259 }
260
261 /// The optional metadata header included in [`FilelogRevisionData`].
262 pub struct FilelogRevisionMetadata<'a>(Option<&'a [u8]>);
263
264 /// Fields parsed from [`FilelogRevisionMetadata`].
265 #[derive(Debug, PartialEq, Default)]
266 pub struct FilelogRevisionMetadataFields<'a> {
267 /// True if the file revision data is censored.
268 pub censored: bool,
269 /// Path of the copy source.
270 pub copy: Option<&'a HgPath>,
271 /// Filelog node ID of the copy source.
272 pub copyrev: Option<Node>,
273 }
274
275 impl<'a> FilelogRevisionMetadata<'a> {
276 /// Parses the metadata fields.
277 pub fn parse(self) -> Result<FilelogRevisionMetadataFields<'a>, HgError> {
278 let mut fields = FilelogRevisionMetadataFields::default();
279 if let Some(metadata) = self.0 {
280 let mut rest = metadata;
281 while !rest.is_empty() {
282 let Some(colon_idx) = memchr::memchr(b':', rest) else {
283 return Err(HgError::corrupted(
284 "File metadata header line missing colon",
285 ));
286 };
287 if rest.get(colon_idx + 1) != Some(&b' ') {
288 return Err(HgError::corrupted(
289 "File metadata header line missing space",
290 ));
291 }
292 let key = &rest[..colon_idx];
293 rest = &rest[colon_idx + 2..];
294 let Some(newline_idx) = memchr::memchr(b'\n', rest) else {
295 return Err(HgError::corrupted(
296 "File metadata header line missing newline",
297 ));
298 };
299 let value = &rest[..newline_idx];
300 match key {
301 b"censored" => {
302 match value {
303 b"" => fields.censored = true,
304 _ => return Err(HgError::corrupted(
305 "File metadata header 'censored' field has nonempty value",
306 )),
307 }
308 }
309 b"copy" => fields.copy = Some(HgPath::new(value)),
310 b"copyrev" => {
311 fields.copyrev = Some(Node::from_hex_for_repo(value)?)
312 }
313 _ => {
314 return Err(HgError::corrupted(
315 format!(
316 "File metadata header has unrecognized key '{}'",
317 String::from_utf8_lossy(key),
318 ),
319 ))
320 }
321 }
322 rest = &rest[newline_idx + 1..];
323 }
324 }
325 Ok(fields)
326 }
327 }
328
329 #[cfg(test)]
330 mod tests {
331 use super::*;
332 use format_bytes::format_bytes;
333
334 #[test]
335 fn test_parse_no_metadata() {
336 let data = FilelogRevisionData(b"data".to_vec());
337 let fields = data.metadata().unwrap().parse().unwrap();
338 assert_eq!(fields, Default::default());
339 }
340
341 #[test]
342 fn test_parse_empty_metadata() {
343 let data = FilelogRevisionData(b"\x01\n\x01\ndata".to_vec());
344 let fields = data.metadata().unwrap().parse().unwrap();
345 assert_eq!(fields, Default::default());
346 }
347
348 #[test]
349 fn test_parse_one_field() {
350 let data =
351 FilelogRevisionData(b"\x01\ncopy: foo\n\x01\ndata".to_vec());
352 let fields = data.metadata().unwrap().parse().unwrap();
353 assert_eq!(
354 fields,
355 FilelogRevisionMetadataFields {
356 copy: Some(HgPath::new("foo")),
357 ..Default::default()
358 }
359 );
360 }
361
362 #[test]
363 fn test_parse_all_fields() {
364 let sha = b"215d5d1546f82a79481eb2df513a7bc341bdf17f";
365 let data = FilelogRevisionData(format_bytes!(
366 b"\x01\ncensored: \ncopy: foo\ncopyrev: {}\n\x01\ndata",
367 sha
368 ));
369 let fields = data.metadata().unwrap().parse().unwrap();
370 assert_eq!(
371 fields,
372 FilelogRevisionMetadataFields {
373 censored: true,
374 copy: Some(HgPath::new("foo")),
375 copyrev: Some(Node::from_hex(sha).unwrap()),
376 }
377 );
378 }
379
380 #[test]
381 fn test_parse_invalid_metadata() {
382 let data =
383 FilelogRevisionData(b"\x01\nbad: value\n\x01\ndata".to_vec());
384 let err = data.metadata().unwrap().parse().unwrap_err();
385 assert!(err.to_string().contains("unrecognized key 'bad'"));
386 }
387 }
General Comments 0
You need to be logged in to leave comments. Login now