diff rust/hg-core/src/revlog/filelog.rs @ 47961:4d2a5ca060e3

rust: Add a Filelog struct that wraps Revlog Some filelog-specific logic is moved from code `rhg cat` into this struct where it can better be reused. Additionally, a missing end delimiter for metadata causes an error to be returned instead of being silently ignored. Differential Revision: https://phab.mercurial-scm.org/D11408
author Simon Sapin <simon.sapin@octobus.net>
date Mon, 13 Sep 2021 15:42:39 +0200
parents
children 001d747c2baf
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hg-core/src/revlog/filelog.rs	Mon Sep 13 15:42:39 2021 +0200
@@ -0,0 +1,79 @@
+use crate::errors::HgError;
+use crate::repo::Repo;
+use crate::revlog::path_encode::path_encode;
+use crate::revlog::revlog::{Revlog, RevlogError};
+use crate::revlog::NodePrefix;
+use crate::revlog::Revision;
+use crate::utils::files::get_path_from_bytes;
+use crate::utils::hg_path::HgPath;
+use crate::utils::SliceExt;
+use std::borrow::Cow;
+use std::path::PathBuf;
+
+/// A specialized `Revlog` to work with file data logs.
+pub struct Filelog {
+    /// The generic `revlog` format.
+    revlog: Revlog,
+}
+
+impl Filelog {
+    pub fn open(repo: &Repo, file_path: &HgPath) -> Result<Self, RevlogError> {
+        let index_path = store_path(file_path, b".i");
+        let data_path = store_path(file_path, b".d");
+        let revlog = Revlog::open(repo, index_path, Some(&data_path))?;
+        Ok(Self { revlog })
+    }
+
+    /// The given node ID is that of the file as found in a manifest, not of a
+    /// changeset.
+    pub fn get_node(
+        &self,
+        file_node: impl Into<NodePrefix>,
+    ) -> Result<FilelogEntry, RevlogError> {
+        let file_rev = self.revlog.get_node_rev(file_node.into())?;
+        self.get_rev(file_rev)
+    }
+
+    /// The given revision is that of the file as found in a manifest, not of a
+    /// changeset.
+    pub fn get_rev(
+        &self,
+        file_rev: Revision,
+    ) -> Result<FilelogEntry, RevlogError> {
+        let data = self.revlog.get_rev_data(file_rev)?;
+        Ok(FilelogEntry(data.into()))
+    }
+}
+
+fn store_path(hg_path: &HgPath, suffix: &[u8]) -> PathBuf {
+    let encoded_bytes =
+        path_encode(&[b"data/", hg_path.as_bytes(), suffix].concat());
+    get_path_from_bytes(&encoded_bytes).into()
+}
+
+pub struct FilelogEntry<'filelog>(Cow<'filelog, [u8]>);
+
+impl<'filelog> FilelogEntry<'filelog> {
+    /// Split into metadata and data
+    pub fn split(&self) -> Result<(Option<&[u8]>, &[u8]), HgError> {
+        const DELIMITER: &[u8; 2] = &[b'\x01', b'\n'];
+
+        if let Some(rest) = self.0.drop_prefix(DELIMITER) {
+            if let Some((metadata, data)) = rest.split_2_by_slice(DELIMITER) {
+                Ok((Some(metadata), data))
+            } else {
+                Err(HgError::corrupted(
+                    "Missing metadata end delimiter in filelog entry",
+                ))
+            }
+        } else {
+            Ok((None, &self.0))
+        }
+    }
+
+    /// Returns the file contents at this revision, stripped of any metadata
+    pub fn data(&self) -> Result<&[u8], HgError> {
+        let (_metadata, data) = self.split()?;
+        Ok(data)
+    }
+}