diff --git a/rust/rhgitaly/src/mercurial.rs b/rust/rhgitaly/src/mercurial.rs
index b4465e96862f36d6ecb401c4421b4ccc8a274630_cnVzdC9yaGdpdGFseS9zcmMvbWVyY3VyaWFsLnJz..74c6bbd8b086214d3ba1e8bf14f0f4827c479310_cnVzdC9yaGdpdGFseS9zcmMvbWVyY3VyaWFsLnJz 100644
--- a/rust/rhgitaly/src/mercurial.rs
+++ b/rust/rhgitaly/src/mercurial.rs
@@ -12,6 +12,6 @@
 use hg::revlog::{Node, NodePrefix, RevlogError, NULL_REVISION};
 use hg::utils::hg_path::HgPath;
 
-use crate::gitaly::ObjectType;
+use crate::gitaly::{tree_entry::EntryType, ObjectType, TreeEntry};
 
 use super::git;
@@ -16,6 +16,6 @@
 
 use super::git;
-use super::oid::blob_oid;
+use super::oid::{blob_oid, tree_oid};
 use tracing::debug;
 
 #[derive(Debug, Default)]
@@ -274,7 +274,6 @@
 where
     IM: Iterator<Item = Result<ManifestEntry<'m>, HgError>>,
 {
-    #[allow(dead_code)]
     fn new(manifest_iter: IM, path: &'a [u8]) -> Self {
         ManifestDirIterator {
             path,
@@ -288,7 +287,6 @@
     /// This is meant so that, if `self.path` is `"foo"`, then `"foo/bar"[self.path_len()..]` is
     /// `"bar"`. It also handles the case where `self.path` is empty correctly, sparing the callers
     /// to check for this special case.
-    #[allow(dead_code)]
     fn prefix_len(&self) -> usize {
         let pl = self.path.len();
         if pl == 0 {
@@ -346,6 +344,151 @@
         None
     }
 }
+
+/// Result of splitting a path for first and last segment
+struct PathWithTopLevelDir<'a> {
+    /// The full path, for flat_path computation
+    #[allow(dead_code)]
+    path: &'a [u8],
+    /// The top level directory in the path (first segment)
+    top_level: &'a [u8],
+}
+
+/// Analyze the given `sub_path`, splitting the directory part to get its top-level.
+///
+/// The given (full) `entry_path` is for error messages only.
+///
+/// If `sub_path` is a top-level file, `None` is returned. Otherwise, the returned
+/// [`PathWithTopLevelDir`] encloses the directory part of `sub_path`.
+///
+/// The computation relies on expectations for Mercurial manifeset entries, which are reflected
+/// by the returned errors. Using the full entry path here should help with error inspection.
+fn split_dir_and_top_level<'a>(
+    sub_path: &'a [u8],
+    entry_path: &[u8],
+) -> Result<Option<PathWithTopLevelDir<'a>>, HgError> {
+    let mut rsplit = sub_path.rsplitn(2, |c| *c == b'/');
+    rsplit.next().ok_or_else(|| {
+        HgError::corrupted(format!(
+            "Manifest entry with trailing slash: {:?}",
+            entry_path
+        ))
+    })?;
+    rsplit
+        .next()
+        .map(|dir_path| {
+            let top_level = dir_path.splitn(2, |c| *c == b'/').next().ok_or_else(|| {
+                HgError::corrupted(format!(
+                    "Manifest entry with double slash: {:?}",
+                    entry_path
+                ))
+            })?;
+            Ok(PathWithTopLevelDir {
+                top_level,
+                path: dir_path,
+            })
+        })
+        .transpose()
+}
+
+/// Iterator yielding a [`TreeEntry`] without flat path for each top-level file or directory in a
+/// manifest directory.
+pub struct DirIteratorWithoutFlatPaths<'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],
+}
+
+impl<'a, 'm, IM> DirIteratorWithoutFlatPaths<'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: &[],
+        }
+    }
+
+    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(),
+        })
+    }
+}
+
+impl<'a, 'm, IM> Iterator for DirIteratorWithoutFlatPaths<'a, 'm, IM>
+where
+    IM: Iterator<Item = Result<ManifestEntry<'m>, HgError>>,
+{
+    type Item = Result<TreeEntry, HgError>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        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()..];
+                    match split_dir_and_top_level(sp, ep) {
+                        Ok(None) => match git_perms(&entry) {
+                            Err(e) => return Some(Err(e)),
+                            Ok(mode) => {
+                                return Some(self.tree_entry(
+                                    ep,
+                                    blob_oid(&self.changeset_node, ep),
+                                    EntryType::Blob,
+                                    mode,
+                                ));
+                            }
+                        },
+                        Ok(Some(split)) => {
+                            if split.top_level != self.current_subdir {
+                                self.current_subdir = split.top_level;
+                                let full_path = &ep
+                                    [..self.manifest_dir_iter.prefix_len() + split.top_level.len()];
+                                return Some(self.tree_entry(
+                                    full_path,
+                                    tree_oid(&self.changeset_node, full_path),
+                                    EntryType::Tree,
+                                    git::OBJECT_MODE_TREE,
+                                ));
+                            }
+                        }
+                        Err(e) => return Some(Err(e)),
+                    }
+                }
+            }
+        }
+        None
+    }
+}
+
 #[cfg(test)]
 mod tests {
 
@@ -511,4 +654,92 @@
             ]
         );
     }
+
+    fn testing_blob_flat_path(cs_node: &Node, path: &[u8], flat_path: &[u8]) -> TreeEntry {
+        TreeEntry {
+            path: path.to_vec(),
+            r#type: EntryType::Blob as i32,
+            mode: git::OBJECT_MODE_NON_EXECUTABLE,
+            commit_oid: "branch/test".to_owned(),
+            root_oid: tree_oid(cs_node, b""),
+            oid: blob_oid(cs_node, path),
+            flat_path: flat_path.to_vec(),
+        }
+    }
+
+    fn testing_blob(cs_node: &Node, path: &[u8]) -> TreeEntry {
+        testing_blob_flat_path(cs_node, path, &[])
+    }
+
+    fn testing_tree_flat_path(cs_node: &Node, path: &[u8], flat_path: &[u8]) -> TreeEntry {
+        TreeEntry {
+            path: path.to_vec(),
+            r#type: EntryType::Tree as i32,
+            mode: git::OBJECT_MODE_TREE,
+            commit_oid: "branch/test".to_owned(),
+            root_oid: tree_oid(cs_node, b""),
+            oid: tree_oid(cs_node, path),
+            flat_path: flat_path.to_vec(),
+        }
+    }
+
+    fn testing_tree(cs_node: &Node, path: &[u8]) -> TreeEntry {
+        testing_tree_flat_path(cs_node, path, &[])
+    }
+
+    #[test]
+    fn test_dir_iterator_without_flat_paths() {
+        let cs_node = Node::from_hex(b"1234567812345678123456781234567812345678").unwrap();
+
+        let manifest = paths_manifest(vec!["foo/a"]);
+        let iter = DirIteratorWithoutFlatPaths::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"),]);
+
+        let manifest = paths_manifest(vec![
+            "foo.",
+            "foo/a",
+            "foo/sub/a",
+            "foo/sub/b",
+            "foo/subb",
+            "foo0",
+        ]);
+        let iter = DirIteratorWithoutFlatPaths::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/subb"),
+            ]
+        );
+
+        let manifest = paths_manifest(vec!["foo.", "foo/a", "foo/sub/ssub/b", "foo/subb", "foo0"]);
+        let iter = DirIteratorWithoutFlatPaths::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/subb"),
+            ]
+        );
+    }
 }
diff --git a/rust/rhgitaly/src/util.rs b/rust/rhgitaly/src/util.rs
index b4465e96862f36d6ecb401c4421b4ccc8a274630_cnVzdC9yaGdpdGFseS9zcmMvdXRpbC5ycw==..74c6bbd8b086214d3ba1e8bf14f0f4827c479310_cnVzdC9yaGdpdGFseS9zcmMvdXRpbC5ycw== 100644
--- a/rust/rhgitaly/src/util.rs
+++ b/rust/rhgitaly/src/util.rs
@@ -38,4 +38,6 @@
                 None => {
                     if *fc == b'/' {
                         latest_slash = Some(i);
+                    } else {
+                        break;
                     }
@@ -41,5 +43,4 @@
                     }
-                    else{ break;}
                 }
                 Some(sc) => {
                     if *fc != *sc {