# HG changeset patch # User Georges Racinet <georges.racinet@octobus.net> # Date 1693435147 -7200 # Thu Aug 31 00:39:07 2023 +0200 # Branch stable # Node ID 934afef8417338bfe88527f098fd677dbc47849e # Parent 74c6bbd8b086214d3ba1e8bf14f0f4827c479310 rhgitaly::mercurial::RecursiveDirIterator This is the core engine for one of the three modes of `CommitService.GetTreeEntries`. diff --git a/rust/rhgitaly/src/mercurial.rs b/rust/rhgitaly/src/mercurial.rs --- a/rust/rhgitaly/src/mercurial.rs +++ b/rust/rhgitaly/src/mercurial.rs @@ -5,6 +5,8 @@ // // SPDX-License-Identifier: GPL-2.0-or-later //! Utilities for Mercurial content handling +use std::collections::VecDeque; + use hg::changelog::Changelog; use hg::errors::HgError; use hg::manifest::{Manifest, ManifestEntry, Manifestlog}; @@ -13,6 +15,7 @@ use hg::utils::hg_path::HgPath; use crate::gitaly::{tree_entry::EntryType, ObjectType, TreeEntry}; +use crate::util::common_subpath_split; use super::git; use super::oid::{blob_oid, tree_oid}; @@ -489,6 +492,119 @@ } } +pub struct RecursiveDirIterator<'a, 'm, IM> +where + IM: Iterator<Item = Result<ManifestEntry<'m>, HgError>>, +{ + /// Is expected to be the repetition of an incoming request "oid" field, + /// often something like `branch/default` and not a real OID + commit_oid: String, + changeset_node: Node, + root_oid: String, + manifest_dir_iter: ManifestDirIterator<'a, 'm, IM>, + current_subdir: &'m [u8], + to_yield: VecDeque<Result<TreeEntry, HgError>>, +} + +impl<'a, 'm, IM> RecursiveDirIterator<'a, 'm, IM> +where + IM: Iterator<Item = Result<ManifestEntry<'m>, HgError>>, +{ + pub fn new(commit_oid: String, changeset_node: Node, manifest: IM, path: &'a [u8]) -> Self { + let root_oid = tree_oid(&changeset_node, b""); + Self { + commit_oid, + changeset_node, + root_oid, + manifest_dir_iter: ManifestDirIterator::new(manifest, path), + current_subdir: "".as_ref(), + /// Queue of `TreeEntry` messages that have to be emitted. + /// + /// Each manifest entry can give rise to several `TreeEntry` messages to yield, + /// because of intermediate directories. Hence we store them in this queue. + /// + /// Each call to `next()` pops either pops from the queue or consumes one or several + /// manifest entries, possibly pushing several more entries in the queue beside yielding + /// one. + to_yield: VecDeque::new(), + } + } + + fn tree_entry( + &self, + path: &[u8], + oid: String, + obj_type: EntryType, + mode: i32, + ) -> Result<TreeEntry, HgError> { + Ok(TreeEntry { + oid, + mode, + commit_oid: self.commit_oid.clone(), + root_oid: self.root_oid.clone(), + path: path.to_vec(), + r#type: obj_type as i32, + flat_path: Vec::new(), // resulting flat_path is always default for recursive queries + }) + } +} + +impl<'a, 'm, IM> Iterator for RecursiveDirIterator<'a, 'm, IM> +where + IM: Iterator<Item = Result<ManifestEntry<'m>, HgError>>, +{ + type Item = Result<TreeEntry, HgError>; + + fn next(&mut self) -> Option<Self::Item> { + if let Some(entry) = self.to_yield.pop_front() { + return Some(entry); + } + + while let Some(entry_res) = self.manifest_dir_iter.next() { + match entry_res { + Err(e) => { + return Some(Err(e)); + } + Ok(entry) => { + let ep = entry.path.as_bytes(); + let sp = &ep[self.manifest_dir_iter.prefix_len()..]; + + let (mut subdir, rem_idx) = common_subpath_split(self.current_subdir, sp); + for i in rem_idx..sp.len() { + if sp[i] != b'/' { + continue; + } + subdir = &sp[..i]; + let fulldir = &ep[..i + self.manifest_dir_iter.prefix_len()]; + self.to_yield.push_back(self.tree_entry( + fulldir, + tree_oid(&self.changeset_node, fulldir), + EntryType::Tree, + git::OBJECT_MODE_TREE, + )); + } + self.current_subdir = subdir; + + match git_perms(&entry) { + Err(e) => { + self.to_yield.push_back(Err(e)); + } + Ok(mode) => { + self.to_yield.push_back(self.tree_entry( + ep, + blob_oid(&self.changeset_node, ep), + EntryType::Blob, + mode, + )); + } + } + } + } + } + self.to_yield.pop_front() + } +} + #[cfg(test)] mod tests { @@ -742,4 +858,74 @@ ] ); } + + #[test] + fn test_recursive_dir_iter() { + let cs_node = Node::from_hex(b"1234567812345678123456781234567812345678").unwrap(); + + let manifest = paths_manifest(vec!["foo/a"]); + let iter = + RecursiveDirIterator::new("branch/test".to_owned(), cs_node, manifest.into_iter(), &[]); + let res: Vec<TreeEntry> = iter.map(|r| r.unwrap()).collect(); + assert_eq!( + res, + vec![ + testing_tree(&cs_node, b"foo"), + testing_blob(&cs_node, b"foo/a"), + ] + ); + + let manifest = paths_manifest(vec![ + "foo.", + "foo/a", + "foo/sub/a", + "foo/sub/b", + "foo/subb", + "foo0", + ]); + let iter = RecursiveDirIterator::new( + "branch/test".to_owned(), + cs_node, + manifest.into_iter(), + b"foo", + ); + let res: Vec<TreeEntry> = iter.map(|r| r.unwrap()).collect(); + assert_eq!( + res, + vec![ + testing_blob(&cs_node, b"foo/a"), + testing_tree(&cs_node, b"foo/sub"), + testing_blob(&cs_node, b"foo/sub/a"), + testing_blob(&cs_node, b"foo/sub/b"), + testing_blob(&cs_node, b"foo/subb"), + ] + ); + + let manifest = paths_manifest(vec![ + "foo.", + "foo/a", + "foo/sub/a", + "foo/sub/ssub/b", + "foo/subb", + "foo0", + ]); + let iter = RecursiveDirIterator::new( + "branch/test".to_owned(), + cs_node, + manifest.into_iter(), + b"foo", + ); + let res: Vec<TreeEntry> = iter.map(|r| r.unwrap()).collect(); + assert_eq!( + res, + vec![ + testing_blob(&cs_node, b"foo/a"), + testing_tree(&cs_node, b"foo/sub"), + testing_blob(&cs_node, b"foo/sub/a"), + testing_tree(&cs_node, b"foo/sub/ssub"), + testing_blob(&cs_node, b"foo/sub/ssub/b"), + testing_blob(&cs_node, b"foo/subb"), + ] + ); + } }