/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

mod diff;
mod iter;
mod link;
mod matcher;
mod namecmp;
mod store;
#[cfg(any(test, feature = "for-tests"))]
pub mod testutil;

use std::collections::btree_map::Entry;
use std::collections::BTreeMap;
use std::fmt;
use std::sync::Arc;

use anyhow::Result;
use iter::bfs_iter;
use manifest::DiffEntry;
use manifest::DirDiffEntry;
use manifest::Directory;
use manifest::File;
use manifest::FileMetadata;
pub use manifest::FileType;
use manifest::FsNodeMetadata;
use manifest::List;
pub use manifest::Manifest;
use minibytes::Bytes;
use once_cell::sync::OnceCell;
use parking_lot::RwLock;
use pathmatcher::Matcher;
use sha1::Digest;
use sha1::Sha1;
pub use store::Flag;
use storemodel::TreeFormat;
use thiserror::Error;
use types::HgId;
pub use types::PathComponent;
pub use types::PathComponentBuf;
use types::RepoPath;
use types::RepoPathBuf;

pub use self::diff::Diff;
pub(crate) use self::link::Link;
pub use self::store::Element as TreeElement;
pub use self::store::Entry as TreeEntry;
pub use self::store::TreeStore;
use crate::iter::DfsCursor;
use crate::iter::Step;
use crate::link::DirLink;
use crate::link::Durable;
use crate::link::DurableEntry;
use crate::link::Ephemeral;
use crate::link::Leaf;
pub use crate::matcher::ManifestMatcher;
use crate::store::InnerStore;

/// The Tree implementation of a Manifest dedicates an inner node for each directory in the
/// repository and a leaf for each file.
#[derive(Clone)]
pub struct TreeManifest {
    store: InnerStore,
    // TODO: root can't be a Leaf
    root: Link,
}

#[derive(Error, Debug)]
#[error("failure inserting '{path}' in manifest")]
pub struct InsertError {
    pub path: RepoPathBuf,
    pub file_metadata: FileMetadata,
    pub source: InsertErrorCause,
}

impl InsertError {
    pub fn new(path: RepoPathBuf, file_metadata: FileMetadata, source: InsertErrorCause) -> Self {
        Self {
            path,
            file_metadata,
            source,
        }
    }
}

#[derive(thiserror::Error, Debug)]
pub enum InsertErrorCause {
    #[error("'{0}' is already a file")]
    ParentFileExists(RepoPathBuf),
    #[error("file path is already a directory")]
    DirectoryExistsForPath,
}

impl TreeManifest {
    /// Instantiates a tree manifest that was stored with the specificed `HgId`
    pub fn durable(store: Arc<dyn TreeStore + Send + Sync>, hgid: HgId) -> Self {
        TreeManifest {
            store: InnerStore::new(store),
            root: Link::durable(hgid),
        }
    }

    /// Instantiates a new tree manifest with no history
    pub fn ephemeral(store: Arc<dyn TreeStore + Send + Sync>) -> Self {
        TreeManifest {
            store: InnerStore::new(store),
            root: Link::ephemeral(),
        }
    }

    fn root_cursor<'a>(&'a self) -> DfsCursor<'a> {
        DfsCursor::new(&self.store, RepoPathBuf::new(), &self.root)
    }
}

impl Manifest for TreeManifest {
    fn get(&self, path: &RepoPath) -> Result<Option<FsNodeMetadata>> {
        let result = self.get_link(path)?.map(|link| link.to_fs_node());
        Ok(result)
    }

    fn get_ignore_case(&self, path: &RepoPath) -> Result<Option<FsNodeMetadata>> {
        let result = self.get_link(path)?.map(|link| link.to_fs_node());
        Ok(result)
    }

    fn list(&self, path: &RepoPath) -> Result<List> {
        let directory = match self.get_link(path)? {
            None => return Ok(List::NotFound),
            Some(l) => match l.as_ref() {
                Leaf(_) => return Ok(List::File),
                Ephemeral(content) => content,
                Durable(entry) => entry.materialize_links(&self.store, path)?,
            },
        };

        let directory = directory
            .into_iter()
            .map(|(key, value)| (key.to_owned(), value.to_fs_node()))
            .collect();

        Ok(List::Directory(directory))
    }

    fn insert(&mut self, path: RepoPathBuf, file_metadata: FileMetadata) -> Result<()> {
        let mut cursor = &self.root;
        let mut must_insert = false;
        for (parent, component) in path.parents().zip(path.components()) {
            let child = match cursor.as_ref() {
                Leaf(_) => Err(InsertError::new(
                    path.clone(), // TODO: get rid of clone (it is borrowed)
                    file_metadata,
                    InsertErrorCause::ParentFileExists(parent.to_owned()),
                ))?,
                Ephemeral(links) => links.get(component),
                Durable(ref entry) => {
                    let links = entry.materialize_links(&self.store, parent)?;
                    links.get(component)
                }
            };
            match child {
                None => {
                    must_insert = true;
                    break;
                }
                Some(link) => cursor = link,
            }
        }
        if must_insert == false {
            match cursor.as_ref() {
                Leaf(existing_metadata) => {
                    if *existing_metadata == file_metadata {
                        return Ok(()); // nothing to do
                    }
                }
                Ephemeral(_) | Durable(_) => Err(InsertError::new(
                    path.clone(), // TODO: get rid of clone (it is borrowed later)
                    file_metadata,
                    InsertErrorCause::DirectoryExistsForPath,
                ))?,
            }
        }
        let (path_parent, last_component) = path.split_last_component().unwrap();
        let mut cursor = &mut self.root;
        // unwrap is fine because root would have been a directory
        for (parent, component) in path_parent.parents().zip(path_parent.components()) {
            cursor = cursor
                .mut_ephemeral_links(&self.store, parent)?
                .entry(component.to_owned())
                .or_insert_with(|| Link::ephemeral());
        }
        match cursor
            .mut_ephemeral_links(&self.store, path_parent)?
            .entry(last_component.to_owned())
        {
            Entry::Vacant(entry) => {
                entry.insert(Link::leaf(file_metadata));
            }
            Entry::Occupied(mut entry) => {
                if let Leaf(ref mut store_ref) = entry.get_mut().as_mut_ref()? {
                    *store_ref = file_metadata;
                } else {
                    unreachable!("Unexpected directory found while insert.");
                }
            }
        }
        Ok(())
    }

    fn remove(&mut self, path: &RepoPath) -> Result<Option<FileMetadata>> {
        // The return value lets us know if there are no more files in the subtree and we should be
        // removing it.
        fn do_remove<'a, I>(store: &InnerStore, cursor: &mut Link, iter: &mut I) -> Result<bool>
        where
            I: Iterator<Item = (&'a RepoPath, &'a PathComponent)>,
        {
            match iter.next() {
                None => {
                    if let Leaf(_) = cursor.as_ref() {
                        // We reached the file that we want to remove.
                        Ok(true)
                    } else {
                        unreachable!("Unexpected directory found while remove.");
                    }
                }
                Some((parent, component)) => {
                    // TODO: only convert to ephemeral if a removal took place
                    // We are navigating the tree down following parent directories
                    let ephemeral_links = cursor.mut_ephemeral_links(&store, parent)?;
                    // When there is no `component` subtree we behave like the file was removed.
                    if let Some(link) = ephemeral_links.get_mut(component) {
                        if do_remove(store, link, iter)? {
                            // There are no files in the component subtree so we remove it.
                            ephemeral_links.remove(component);
                        }
                    }
                    Ok(ephemeral_links.is_empty())
                }
            }
        }
        if let Some(file_metadata) = self.get_file(path)? {
            do_remove(
                &self.store,
                &mut self.root,
                &mut path.parents().zip(path.components()),
            )?;
            Ok(Some(file_metadata))
        } else {
            Ok(None)
        }
    }

    /// Write dirty trees using specified format to disk. Return the root tree id.
    fn flush(&mut self) -> Result<HgId> {
        fn compute_sha1(content: &[u8], format: TreeFormat) -> HgId {
            let mut hasher = Sha1::new();
            match format {
                TreeFormat::Git => hasher.update(format!("tree {}\0", content.len())),
                TreeFormat::Hg => {
                    // XXX: No p1, p2 to produce a genuine SHA1.
                    // This code path is only meaningful for tests.
                    assert!(
                        cfg!(test),
                        "flush() cannot be used with hg store, consider finalize() instead"
                    );
                }
            }
            hasher.update(content);
            let buf: [u8; HgId::len()] = hasher.finalize().into();
            (&buf).into()
        }
        fn do_flush<'a, 'b, 'c>(
            store: &'a InnerStore,
            pathbuf: &'b mut RepoPathBuf,
            cursor: &'c mut Link,
            format: TreeFormat,
        ) -> Result<(HgId, store::Flag)> {
            loop {
                let new_cursor = match cursor.as_mut_ref()? {
                    Leaf(file_metadata) => {
                        return Ok((
                            file_metadata.hgid.clone(),
                            store::Flag::File(file_metadata.file_type.clone()),
                        ));
                    }
                    Durable(entry) => return Ok((entry.hgid.clone(), store::Flag::Directory)),
                    Ephemeral(links) => {
                        let iter = links.iter_mut().map(|(component, link)| {
                            pathbuf.push(component.as_path_component());
                            let (hgid, flag) = do_flush(store, pathbuf, link, format)?;
                            pathbuf.pop();
                            Ok(store::Element::new(
                                component.to_owned(),
                                hgid.clone(),
                                flag,
                            ))
                        });
                        let elements: Vec<_> = iter.collect::<Result<Vec<_>>>()?;
                        let entry = store::Entry::from_elements(elements, format);
                        let hgid = compute_sha1(entry.as_ref(), format);
                        store.insert_entry(&pathbuf, hgid, entry)?;

                        let cell = OnceCell::new();
                        // TODO: remove clone
                        cell.set(links.clone()).unwrap();

                        let durable_entry = DurableEntry { hgid, links: cell };
                        Link::new(Durable(Arc::new(durable_entry)))
                    }
                };
                *cursor = new_cursor;
            }
        }
        let mut path = RepoPathBuf::new();
        let format = self.store.format();
        let (hgid, _) = do_flush(&self.store, &mut path, &mut self.root, format)?;
        Ok(hgid)
    }

    #[tracing::instrument(skip_all)]
    fn files<'a, M: 'static + Matcher + Sync + Send>(
        &'a self,
        matcher: M,
    ) -> Box<dyn Iterator<Item = Result<File>> + 'a> {
        let files =
            bfs_iter(self.store.clone(), &[&self.root], matcher).filter_map(
                |result| match result {
                    Ok((path, FsNodeMetadata::File(metadata))) => {
                        Some(Ok(File::new(path, metadata)))
                    }
                    Ok(_) => None,
                    Err(e) => Some(Err(e)),
                },
            );
        Box::new(files)
    }

    /// Returns an iterator over all the directories that are present in the
    /// tree.
    ///
    /// Note: the matcher should be a prefix matcher, other kinds of matchers
    /// could be less effective than expected.
    #[tracing::instrument(skip_all)]
    fn dirs<'a, M: 'static + Matcher + Sync + Send>(
        &'a self,
        matcher: M,
    ) -> Box<dyn Iterator<Item = Result<Directory>> + 'a> {
        let dirs =
            bfs_iter(self.store.clone(), &[&self.root], matcher).filter_map(
                |result| match result {
                    Ok((path, FsNodeMetadata::Directory(metadata))) => {
                        Some(Ok(Directory::new(path, metadata)))
                    }
                    Ok(_) => None,
                    Err(e) => Some(Err(e)),
                },
            );
        Box::new(dirs)
    }

    fn diff<'a, M: Matcher>(
        &'a self,
        other: &'a Self,
        matcher: &'a M,
    ) -> Result<Box<dyn Iterator<Item = Result<DiffEntry>> + 'a>> {
        Ok(Box::new(Diff::new(self, other, matcher)?))
    }

    fn modified_dirs<'a, M: Matcher>(
        &'a self,
        other: &'a Self,
        matcher: &'a M,
    ) -> Result<Box<dyn Iterator<Item = Result<DirDiffEntry>> + 'a>> {
        Ok(Box::new(Diff::new(self, other, matcher)?.modified_dirs()))
    }
}

impl fmt::Debug for TreeManifest {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fn write_indent(f: &mut fmt::Formatter<'_>, indent: usize) -> fmt::Result {
            write!(f, "{}", str::repeat("| ", indent))?;
            Ok(())
        }
        fn write_children(
            f: &mut fmt::Formatter<'_>,
            children: &BTreeMap<PathComponentBuf, Link>,
            indent: usize,
        ) -> fmt::Result {
            for (component, link) in children {
                write_indent(f, indent)?;
                write!(f, "{} ", component)?;
                write_links(f, link, indent + 1)?;
            }
            Ok(())
        }
        fn write_links(f: &mut fmt::Formatter<'_>, link: &Link, indent: usize) -> fmt::Result {
            match link.as_ref() {
                Leaf(metadata) => {
                    write!(f, "(File, {}, {:?})\n", metadata.hgid, metadata.file_type)
                }
                Ephemeral(children) => {
                    write!(f, "(Ephemeral)\n")?;
                    write_children(f, children, indent)
                }
                Durable(entry) => {
                    write!(f, "(Durable, {})\n", entry.hgid)?;
                    match entry.links.get() {
                        None => Ok(()),
                        Some(children) => write_children(f, children, indent),
                    }
                }
            }
        }
        write!(f, "Root ")?;
        write_links(f, &self.root, 1)
    }
}

impl TreeManifest {
    /// Produces new trees to write in hg format (path, id, text, p1, p2).
    /// Does not write to the tree store directly.
    pub fn finalize(
        &mut self,
        parent_trees: Vec<&TreeManifest>,
    ) -> Result<impl Iterator<Item = (RepoPathBuf, HgId, Bytes, HgId, HgId)>> {
        fn compute_hgid<C: AsRef<[u8]>>(parent_tree_nodes: &[HgId], content: C) -> HgId {
            let mut hasher = Sha1::new();
            debug_assert!(parent_tree_nodes.len() <= 2);
            let p1 = parent_tree_nodes.get(0).unwrap_or(HgId::null_id());
            let p2 = parent_tree_nodes.get(1).unwrap_or(HgId::null_id());
            // Even if parents are sorted two hashes go into hash computation but surprise
            // the NULL_ID is not a special case in this case and gets sorted.
            if p1 < p2 {
                hasher.update(p1.as_ref());
                hasher.update(p2.as_ref());
            } else {
                hasher.update(p2.as_ref());
                hasher.update(p1.as_ref());
            }
            hasher.update(content.as_ref());
            let buf: [u8; HgId::len()] = hasher.finalize().into();
            (&buf).into()
        }
        struct Executor<'a> {
            store: &'a InnerStore,
            path: RepoPathBuf,
            converted_nodes: Vec<(RepoPathBuf, HgId, Bytes, HgId, HgId)>,
            parent_trees: Vec<DfsCursor<'a>>,
        }
        impl<'a> Executor<'a> {
            fn new(
                store: &'a InnerStore,
                parent_trees: &[&'a TreeManifest],
            ) -> Result<Executor<'a>> {
                let mut executor = Executor {
                    store,
                    path: RepoPathBuf::new(),
                    converted_nodes: Vec::new(),
                    parent_trees: parent_trees.iter().map(|v| v.root_cursor()).collect(),
                };
                // The first node after step is the root directory. `work()` expects cursors to
                // be pointing to the underlying link.
                for cursor in executor.parent_trees.iter_mut() {
                    match cursor.step() {
                        Step::Success | Step::End => {}
                        Step::Err(err) => return Err(err),
                    }
                }
                Ok(executor)
            }
            fn active_parent_tree_nodes(&self, active_parents: &[usize]) -> Result<Vec<HgId>> {
                let mut parent_nodes = Vec::with_capacity(active_parents.len());
                for id in active_parents {
                    let cursor = &self.parent_trees[*id];
                    let hgid = match cursor.link().as_ref() {
                        Leaf(_) | Ephemeral(_) => unreachable!(),
                        Durable(entry) => entry.hgid,
                    };
                    parent_nodes.push(hgid);
                }
                Ok(parent_nodes)
            }
            fn advance_parents(&mut self, active_parents: &[usize]) -> Result<()> {
                for id in active_parents {
                    let cursor = &mut self.parent_trees[*id];
                    match cursor.step() {
                        Step::Success | Step::End => {}
                        Step::Err(err) => return Err(err),
                    }
                }
                Ok(())
            }
            fn parent_trees_for_subdirectory(
                &mut self,
                active_parents: &[usize],
            ) -> Result<Vec<usize>> {
                let mut result = Vec::new();
                for id in active_parents.iter() {
                    let cursor = &mut self.parent_trees[*id];
                    while !cursor.finished() && cursor.path() < self.path.as_repo_path() {
                        cursor.skip_subtree();
                        match cursor.step() {
                            Step::Success | Step::End => {}
                            Step::Err(err) => return Err(err),
                        }
                    }
                    if !cursor.finished() && cursor.path() == self.path.as_repo_path() {
                        match cursor.link().as_ref() {
                            Leaf(_) => {} // files and directories don't share history
                            Durable(_) => result.push(*id),
                            Ephemeral(_) => {
                                panic!("Found ephemeral parent when finalizing manifest.")
                            }
                        }
                    }
                }
                Ok(result)
            }
            fn work(
                &mut self,
                link: &mut Link,
                active_parents: Vec<usize>,
            ) -> Result<(HgId, store::Flag)> {
                let parent_tree_nodes = self.active_parent_tree_nodes(&active_parents)?;
                if let Durable(entry) = link.as_ref() {
                    if parent_tree_nodes.contains(&entry.hgid) {
                        return Ok((entry.hgid, store::Flag::Directory));
                    }
                }
                self.advance_parents(&active_parents)?;
                if let Leaf(file_metadata) = link.as_ref() {
                    return Ok((
                        file_metadata.hgid,
                        store::Flag::File(file_metadata.file_type.clone()),
                    ));
                }
                // TODO: This code is also used on durable nodes for the purpose of generating
                // a list of entries to insert in the local store. For those cases we don't
                // need to convert to Ephemeral instead only verify the hash.
                let links = link.mut_ephemeral_links(self.store, &self.path)?;
                // finalize() is only used for hg format.
                let format = TreeFormat::Hg;
                let mut entry = store::EntryMut::new(format);
                for (component, link) in links.iter_mut() {
                    self.path.push(component.as_path_component());
                    let child_parents = self.parent_trees_for_subdirectory(&active_parents)?;
                    let (hgid, flag) = self.work(link, child_parents)?;
                    self.path.pop();
                    let element = store::Element::new(component.clone(), hgid, flag);
                    entry.add_element_hg(element);
                }
                let entry = entry.freeze();
                let hgid = compute_hgid(&parent_tree_nodes, &entry);

                let cell = OnceCell::new();
                // TODO: remove clone
                cell.set(links.clone()).unwrap();

                let durable_entry = DurableEntry { hgid, links: cell };
                let inner = Arc::new(durable_entry);
                *link = Link::new(Durable(inner));
                let parent_hgid = |id| *parent_tree_nodes.get(id).unwrap_or(HgId::null_id());
                self.converted_nodes.push((
                    self.path.clone(),
                    hgid,
                    entry.to_bytes(),
                    parent_hgid(0),
                    parent_hgid(1),
                ));
                Ok((hgid, store::Flag::Directory))
            }
        }

        assert_eq!(
            self.store.format(),
            TreeFormat::Hg,
            "finalize() can only be used for hg store, use flush() instead"
        );
        let mut executor = Executor::new(&self.store, &parent_trees)?;
        executor.work(&mut self.root, (0..parent_trees.len()).collect())?;
        Ok(executor.converted_nodes.into_iter())
    }

    fn get_link(&self, path: &RepoPath) -> Result<Option<&Link>> {
        let mut cursor = &self.root;
        for (parent, component) in path.parents().zip(path.components()) {
            let child = match cursor.as_ref() {
                Leaf(_) => return Ok(None),
                Ephemeral(links) => links.get(component),
                Durable(ref entry) => {
                    let links = entry.materialize_links(&self.store, parent)?;
                    links.get(component)
                }
            };
            match child {
                None => return Ok(None),
                Some(link) => cursor = link,
            }
        }
        Ok(Some(cursor))
    }
}

pub trait ReadTreeManifest {
    fn get(&self, commit_id: &HgId) -> Result<Arc<RwLock<TreeManifest>>>;
}

/// The purpose of this function is to provide compatible behavior with the C++ implementation
/// of the treemanifest. This function is problematic because it goes through abstraction
/// boundaries and is built with the assumption that the storage format is the same as the
/// in memory format that is the same as the wire format.
///
/// This function returns the nodes that need to be sent over the wire for a subtree of the
/// manifest to be fully hydrated. The subtree is represented by `path` and `hgid`. The data
/// that is present locally by the client is represented by `other_nodes`.
///
/// It is undefined what this function will do when called with a path that points to a file
/// or with nodes that don't make sense.
// NOTE: The implementation is currently custom. Consider converting the code to use Cursor.
// The suggestion received in code review was also to consider making the return type more
// simple (RepoPath, HgId) and letting the call sites deal with the Bytes.
pub fn compat_subtree_diff(
    store: Arc<dyn TreeStore + Send + Sync>,
    path: &RepoPath,
    hgid: HgId,
    other_nodes: Vec<HgId>,
    depth: i32,
) -> Result<Vec<(RepoPathBuf, HgId, Vec<HgId>, Bytes)>> {
    struct State {
        store: InnerStore,
        path: RepoPathBuf,
        result: Vec<(RepoPathBuf, HgId, Vec<HgId>, Bytes)>,
        depth_remaining: i32,
    }
    impl State {
        fn work(&mut self, hgid: HgId, other_nodes: Vec<HgId>) -> Result<()> {
            let entry = self.store.get_entry(&self.path, hgid)?;

            if self.depth_remaining > 0 {
                // TODO: optimize "other_nodes" construction
                // We use BTreeMap for convenience only, it is more efficient to use an array since
                // the entries are already sorted.
                let mut others_map = BTreeMap::new();
                for other_hgid in other_nodes.clone() {
                    let other_entry = self.store.get_entry(&self.path, other_hgid)?;
                    for other_element_result in other_entry.elements() {
                        let other_element = other_element_result?;
                        if other_element.flag == store::Flag::Directory {
                            others_map
                                .entry(other_element.component)
                                .or_insert(vec![])
                                .push(other_element.hgid);
                        }
                    }
                }
                for element_result in entry.elements() {
                    let element = element_result?;
                    if element.flag != store::Flag::Directory {
                        continue;
                    }
                    let mut others = others_map
                        .remove(&element.component)
                        .unwrap_or_else(|| vec![]);
                    if others.contains(&element.hgid) {
                        continue;
                    }
                    others.dedup();
                    self.path.push(element.component.as_ref());
                    self.depth_remaining -= 1;
                    self.work(element.hgid, others)?;
                    self.depth_remaining += 1;
                    self.path.pop();
                }
            }
            // NOTE: order in the result set matters for a lot of the integration tests
            self.result
                .push((self.path.clone(), hgid, other_nodes, entry.to_bytes()));
            Ok(())
        }
    }

    if other_nodes.contains(&hgid) {
        return Ok(vec![]);
    }

    let mut state = State {
        store: InnerStore::new(store),
        path: path.to_owned(),
        result: vec![],
        depth_remaining: depth - 1,
    };
    state.work(hgid, other_nodes)?;
    Ok(state.result)
}

/// Prefetch everything under given tree nodes, filtered by the given matcher.
///
/// Server requests are only made for trees not already available locally.
/// Assuming nothing is available locally, prefetch must make O(depth) serial
/// round trips to the server.
pub fn prefetch(
    store: Arc<dyn TreeStore + Send + Sync>,
    mf_nodes: &[HgId],
    matcher: impl 'static + Matcher + Sync + Send,
) -> Result<()> {
    let links: Vec<Link> = mf_nodes.iter().map(|id| Link::durable(*id)).collect();
    for node in bfs_iter(InnerStore::new(store), &links, matcher) {
        node?;
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use manifest::testutil::*;
    use manifest::FileType;
    use store::Element;
    use types::hgid::NULL_ID;
    use types::testutil::*;

    use self::testutil::*;
    use super::*;

    impl store::Entry {
        fn from_elements_hg(elements: Vec<Element>) -> Self {
            Self::from_elements(elements, TreeFormat::Hg)
        }
    }
    fn store_element(path: &str, hex: &str, flag: store::Flag) -> store::Element {
        store::Element::new(path_component_buf(path), hgid(hex), flag)
    }

    fn get_hgid(tree: &TreeManifest, path: &RepoPath) -> HgId {
        match tree.get_link(path).unwrap().unwrap().as_ref() {
            Leaf(file_metadata) => file_metadata.hgid,
            Durable(ref entry) => entry.hgid,
            Ephemeral(_) => {
                panic!("Asked for hgid on path {} but found ephemeral hgid.", path)
            }
        }
    }

    #[test]
    fn test_insert() {
        let mut tree = TreeManifest::ephemeral(Arc::new(TestStore::new()));
        tree.insert(repo_path_buf("foo/bar"), make_meta("10"))
            .unwrap();
        assert_eq!(
            tree.get_file(repo_path("foo/bar")).unwrap(),
            Some(make_meta("10"))
        );
        assert_eq!(tree.get_file(repo_path("baz")).unwrap(), None);

        tree.insert(repo_path_buf("baz"), make_meta("20")).unwrap();
        assert_eq!(
            tree.get_file(repo_path("foo/bar")).unwrap(),
            Some(make_meta("10"))
        );
        assert_eq!(
            tree.get_file(repo_path("baz")).unwrap(),
            Some(make_meta("20"))
        );

        tree.insert(repo_path_buf("foo/bat"), make_meta("30"))
            .unwrap();
        assert_eq!(
            tree.get_file(repo_path("foo/bat")).unwrap(),
            Some(make_meta("30"))
        );
        assert_eq!(
            tree.get_file(repo_path("foo/bar")).unwrap(),
            Some(make_meta("10"))
        );
        assert_eq!(
            tree.get_file(repo_path("baz")).unwrap(),
            Some(make_meta("20"))
        );

        assert_eq!(
            tree.insert(repo_path_buf("foo/bar/error"), make_meta("40"))
                .unwrap_err()
                .chain()
                .map(|e| format!("{}", e))
                .collect::<Vec<_>>(),
            vec![
                "failure inserting 'foo/bar/error' in manifest",
                "\'foo/bar\' is already a file",
            ],
        );
        assert_eq!(
            tree.insert(repo_path_buf("foo"), make_meta("50"))
                .unwrap_err()
                .chain()
                .map(|e| format!("{}", e))
                .collect::<Vec<_>>(),
            vec![
                "failure inserting 'foo' in manifest",
                "file path is already a directory",
            ],
        );
    }

    #[test]
    fn test_durable_link() {
        let store = TestStore::new();
        let root_entry = store::Entry::from_elements_hg(vec![
            store_element("foo", "10", store::Flag::Directory),
            store_element("baz", "20", store::Flag::File(FileType::Regular)),
        ]);
        store
            .insert(RepoPath::empty(), hgid("1"), root_entry.to_bytes())
            .unwrap();
        let foo_entry = store::Entry::from_elements_hg(vec![store_element(
            "bar",
            "11",
            store::Flag::File(FileType::Regular),
        )]);
        store
            .insert(repo_path("foo"), hgid("10"), foo_entry.to_bytes())
            .unwrap();
        let mut tree = TreeManifest::durable(Arc::new(store), hgid("1"));

        assert_eq!(
            tree.get_file(repo_path("foo/bar")).unwrap(),
            Some(make_meta("11"))
        );
        assert_eq!(
            tree.get_file(repo_path("baz")).unwrap(),
            Some(make_meta("20"))
        );

        tree.insert(repo_path_buf("foo/bat"), make_meta("12"))
            .unwrap();
        assert_eq!(
            tree.get_file(repo_path("foo/bat")).unwrap(),
            Some(make_meta("12"))
        );
        assert_eq!(
            tree.get_file(repo_path("foo/bar")).unwrap(),
            Some(make_meta("11"))
        );
        assert_eq!(
            tree.get_file(repo_path("baz")).unwrap(),
            Some(make_meta("20"))
        );
    }

    #[test]
    fn test_insert_into_directory() {
        let mut tree = TreeManifest::ephemeral(Arc::new(TestStore::new()));
        tree.insert(repo_path_buf("foo/bar/baz"), make_meta("10"))
            .unwrap();
        assert!(
            tree.insert(repo_path_buf("foo/bar"), make_meta("20"))
                .is_err()
        );
        assert!(tree.insert(repo_path_buf("foo"), make_meta("30")).is_err());
    }

    #[test]
    fn test_insert_with_file_parent() {
        let mut tree = TreeManifest::ephemeral(Arc::new(TestStore::new()));
        tree.insert(repo_path_buf("foo"), make_meta("10")).unwrap();
        assert!(
            tree.insert(repo_path_buf("foo/bar"), make_meta("20"))
                .is_err()
        );
        assert!(
            tree.insert(repo_path_buf("foo/bar/baz"), make_meta("30"))
                .is_err()
        );
    }

    #[test]
    fn test_get_from_directory() {
        let mut tree = TreeManifest::ephemeral(Arc::new(TestStore::new()));
        tree.insert(repo_path_buf("foo/bar/baz"), make_meta("10"))
            .unwrap();
        assert_eq!(
            tree.get(repo_path("foo/bar")).unwrap(),
            Some(FsNodeMetadata::Directory(None))
        );
        assert_eq!(
            tree.get(repo_path("foo")).unwrap(),
            Some(FsNodeMetadata::Directory(None))
        );
    }

    #[test]
    fn test_get_with_file_parent() {
        let mut tree = TreeManifest::ephemeral(Arc::new(TestStore::new()));
        tree.insert(repo_path_buf("foo"), make_meta("10")).unwrap();
        assert_eq!(tree.get(repo_path("foo/bar")).unwrap(), None);
        assert_eq!(tree.get(repo_path("foo/bar/baz")).unwrap(), None);
    }

    #[test]
    fn test_remove_from_ephemeral() {
        let mut tree = TreeManifest::ephemeral(Arc::new(TestStore::new()));
        tree.insert(repo_path_buf("a1/b1/c1/d1"), make_meta("10"))
            .unwrap();
        tree.insert(repo_path_buf("a1/b2"), make_meta("20"))
            .unwrap();
        tree.insert(repo_path_buf("a2/b2/c2"), make_meta("30"))
            .unwrap();

        assert_eq!(tree.remove(repo_path("a1")).unwrap(), None);
        assert_eq!(tree.remove(repo_path("a1/b1")).unwrap(), None);
        assert_eq!(tree.remove(repo_path("a1/b1/c1/d1/e1")).unwrap(), None);
        assert_eq!(
            tree.remove(repo_path("a1/b1/c1/d1")).unwrap(),
            Some(make_meta("10"))
        );
        assert_eq!(tree.remove(repo_path("a3")).unwrap(), None);
        assert_eq!(tree.remove(repo_path("a1/b3")).unwrap(), None);
        assert_eq!(tree.remove(repo_path("a1/b1/c1/d2")).unwrap(), None);
        assert_eq!(tree.remove(repo_path("a1/b1/c1/d1/e1")).unwrap(), None);
        assert_eq!(tree.remove(RepoPath::empty()).unwrap(), None);
        assert_eq!(tree.get(repo_path("a1/b1/c1/d1")).unwrap(), None);
        assert_eq!(tree.get(repo_path("a1/b1/c1")).unwrap(), None);
        assert_eq!(
            tree.get(repo_path("a1/b2")).unwrap(),
            Some(FsNodeMetadata::File(make_meta("20")))
        );
        assert_eq!(
            tree.remove(repo_path("a1/b2")).unwrap(),
            Some(make_meta("20"))
        );
        assert_eq!(tree.get(repo_path("a1")).unwrap(), None);

        assert_eq!(
            tree.get(repo_path("a2/b2/c2")).unwrap(),
            Some(FsNodeMetadata::File(make_meta("30")))
        );
        assert_eq!(
            tree.remove(repo_path("a2/b2/c2")).unwrap(),
            Some(make_meta("30"))
        );
        assert_eq!(tree.get(repo_path("a2")).unwrap(), None);

        assert_eq!(
            tree.get(RepoPath::empty()).unwrap(),
            Some(FsNodeMetadata::Directory(None))
        );
    }

    #[test]
    fn test_remove_from_durable() {
        let store = TestStore::new();
        let root_entry = store::Entry::from_elements_hg(vec![
            store_element("a1", "10", store::Flag::Directory),
            store_element("a2", "20", store::Flag::File(FileType::Regular)),
        ]);
        let tree_hgid = hgid("1");
        store
            .insert(RepoPath::empty(), tree_hgid, root_entry.to_bytes())
            .unwrap();
        let a1_entry = store::Entry::from_elements_hg(vec![
            store_element("b1", "11", store::Flag::File(FileType::Regular)),
            store_element("b2", "12", store::Flag::File(FileType::Regular)),
        ]);
        store
            .insert(repo_path("a1"), hgid("10"), a1_entry.to_bytes())
            .unwrap();
        let mut tree = TreeManifest::durable(Arc::new(store), tree_hgid);

        assert_eq!(
            tree.get(RepoPath::empty()).unwrap(),
            Some(FsNodeMetadata::Directory(Some(tree_hgid)))
        );
        assert_eq!(tree.remove(repo_path("a1")).unwrap(), None);
        assert_eq!(
            tree.remove(repo_path("a1/b1")).unwrap(),
            Some(make_meta("11"))
        );
        assert_eq!(tree.get(repo_path("a1/b1")).unwrap(), None);
        assert_eq!(
            tree.get(repo_path("a1/b2")).unwrap(),
            Some(FsNodeMetadata::File(make_meta("12")))
        );
        assert_eq!(
            tree.remove(repo_path("a1/b2")).unwrap(),
            Some(make_meta("12"))
        );
        assert_eq!(tree.get(repo_path("a1/b2")).unwrap(), None);
        assert_eq!(tree.get(repo_path("a1")).unwrap(), None);
        assert_eq!(tree.get_link(repo_path("a1")).unwrap(), None);

        assert_eq!(
            tree.get(repo_path("a2")).unwrap(),
            Some(FsNodeMetadata::File(make_meta("20")))
        );
        assert_eq!(tree.remove(repo_path("a2")).unwrap(), Some(make_meta("20")));
        assert_eq!(tree.get(repo_path("a2")).unwrap(), None);
        assert_eq!(
            tree.get(RepoPath::empty()).unwrap(),
            Some(FsNodeMetadata::Directory(None))
        );
    }

    #[test]
    fn test_flush() {
        let store = Arc::new(TestStore::new());
        let mut tree = TreeManifest::ephemeral(store.clone());
        tree.insert(repo_path_buf("a1/b1/c1/d1"), make_meta("10"))
            .unwrap();
        tree.insert(repo_path_buf("a1/b2"), make_meta("20"))
            .unwrap();
        tree.insert(repo_path_buf("a2/b2/c2"), make_meta("30"))
            .unwrap();

        let hgid = tree.flush().unwrap();

        let tree = TreeManifest::durable(store.clone(), hgid);
        assert_eq!(
            tree.get_file(repo_path("a1/b1/c1/d1")).unwrap(),
            Some(make_meta("10"))
        );
        assert_eq!(
            tree.get_file(repo_path("a1/b2")).unwrap(),
            Some(make_meta("20"))
        );
        assert_eq!(
            tree.get_file(repo_path("a2/b2/c2")).unwrap(),
            Some(make_meta("30"))
        );
        assert_eq!(tree.get(repo_path("a2/b1")).unwrap(), None);
    }

    #[test]
    fn test_finalize_with_zero_and_one_parents() {
        let store = Arc::new(TestStore::new());
        let mut tree = TreeManifest::ephemeral(store.clone());
        tree.insert(repo_path_buf("a1/b1/c1/d1"), make_meta("10"))
            .unwrap();
        tree.insert(repo_path_buf("a1/b2"), make_meta("20"))
            .unwrap();
        tree.insert(repo_path_buf("a2/b2/c2"), make_meta("30"))
            .unwrap();
        let tree_changed: Vec<_> = tree.finalize(vec![]).unwrap().collect();

        assert_eq!(tree_changed.len(), 6);
        assert_eq!(tree_changed[0].0, repo_path_buf("a1/b1/c1"));
        assert_eq!(tree_changed[1].0, repo_path_buf("a1/b1"));
        assert_eq!(tree_changed[2].0, repo_path_buf("a1"));
        assert_eq!(tree_changed[3].0, repo_path_buf("a2/b2"));
        assert_eq!(tree_changed[4].0, repo_path_buf("a2"));
        assert_eq!(tree_changed[5].0, RepoPathBuf::new());

        // we should write before we can update
        // depends on the implementation but it is valid for finalize to query the store
        // for the values returned in the previous finalize call

        use minibytes::Bytes;
        for (path, hgid, raw, _, _) in tree_changed.iter() {
            store
                .insert(&path, *hgid, Bytes::copy_from_slice(&raw[..]))
                .unwrap();
        }

        let mut update = tree.clone();
        update
            .insert(repo_path_buf("a1/b2"), make_meta("40"))
            .unwrap();
        update.remove(repo_path("a2/b2/c2")).unwrap();
        update
            .insert(repo_path_buf("a3/b1"), make_meta("50"))
            .unwrap();
        let update_changed: Vec<_> = update.finalize(vec![&tree]).unwrap().collect();
        assert_eq!(update_changed[0].0, repo_path_buf("a1"));
        assert_eq!(update_changed[0].3, tree_changed[2].1);
        assert_eq!(update_changed[0].4, NULL_ID);
        assert_eq!(update_changed[1].0, repo_path_buf("a3"));
        assert_eq!(update_changed[1].3, NULL_ID);
        assert_eq!(update_changed[1].4, NULL_ID);
        assert_eq!(update_changed[2].0, RepoPathBuf::new());
        assert_eq!(update_changed[2].3, tree_changed[5].1);
        assert_eq!(update_changed[2].4, NULL_ID);
    }

    #[test]
    fn test_finalize_merge() {
        let store = Arc::new(TestStore::new());
        let mut p1 = TreeManifest::ephemeral(store.clone());
        p1.insert(repo_path_buf("a1/b1/c1/d1"), make_meta("10"))
            .unwrap();
        p1.insert(repo_path_buf("a1/b2"), make_meta("20")).unwrap();
        p1.insert(repo_path_buf("a2/b2/c2"), make_meta("30"))
            .unwrap();
        let _p1_changed = p1.finalize(vec![]).unwrap();

        let mut p2 = TreeManifest::ephemeral(store.clone());
        p2.insert(repo_path_buf("a1/b2"), make_meta("40")).unwrap();
        p2.insert(repo_path_buf("a3/b1"), make_meta("50")).unwrap();
        let _p2_changed = p2.finalize(vec![]).unwrap();

        let mut tree = p1.clone();
        tree.insert(repo_path_buf("a1/b2"), make_meta("40"))
            .unwrap();
        tree.insert(repo_path_buf("a2/b2/c2"), make_meta("60"))
            .unwrap();
        tree.insert(repo_path_buf("a3/b1"), make_meta("50"))
            .unwrap();
        let tree_changed: Vec<_> = tree.finalize(vec![&p1, &p2]).unwrap().collect();
        assert_eq!(tree_changed[0].0, repo_path_buf("a1"));
        assert_eq!(tree_changed[0].3, get_hgid(&p1, repo_path("a1")));
        assert_eq!(tree_changed[0].4, get_hgid(&p2, repo_path("a1")));

        assert_eq!(tree_changed[1].0, repo_path_buf("a2/b2"));
        assert_eq!(tree_changed[1].3, get_hgid(&p1, repo_path("a2/b2")));
        assert_eq!(tree_changed[1].4, NULL_ID);
        assert_eq!(tree_changed[2].0, repo_path_buf("a2"));
        assert_eq!(tree_changed[3].0, repo_path_buf("a3"));
        assert_eq!(tree_changed[3].3, get_hgid(&p2, repo_path("a3")));
        assert_eq!(tree_changed[3].4, NULL_ID);
        assert_eq!(tree_changed[4].0, RepoPathBuf::new());

        assert_eq!(
            vec![tree_changed[4].3, tree_changed[4].4],
            vec![
                get_hgid(&p1, RepoPath::empty()),
                get_hgid(&p2, RepoPath::empty()),
            ]
        );
    }

    #[test]
    fn test_finalize_file_to_directory() {
        let store = Arc::new(TestStore::new());
        let mut tree1 = TreeManifest::ephemeral(store.clone());
        tree1.insert(repo_path_buf("a1"), make_meta("10")).unwrap();
        let tree1_changed: Vec<_> = tree1.finalize(vec![]).unwrap().collect();
        assert_eq!(tree1_changed[0].0, RepoPathBuf::new());
        assert_eq!(tree1_changed[0].3, NULL_ID);

        let mut tree2 = TreeManifest::ephemeral(store.clone());
        tree2
            .insert(repo_path_buf("a1/b1"), make_meta("20"))
            .unwrap();
        let tree2_changed: Vec<_> = tree2.finalize(vec![&tree1]).unwrap().collect();
        assert_eq!(tree2_changed[0].0, repo_path_buf("a1"));
        assert_eq!(tree2_changed[0].3, NULL_ID);
        assert_eq!(tree2_changed[1].0, RepoPathBuf::new());
        assert_eq!(tree2_changed[1].3, tree1_changed[0].1);
        assert_eq!(tree2_changed[1].4, NULL_ID);

        let mut tree3 = TreeManifest::ephemeral(store.clone());
        tree3.insert(repo_path_buf("a1"), make_meta("30")).unwrap();
        let tree3_changed: Vec<_> = tree3.finalize(vec![&tree2]).unwrap().collect();
        assert_eq!(tree3_changed[0].0, RepoPathBuf::new());
        assert_eq!(tree3_changed[0].3, tree2_changed[1].1);
        assert_eq!(tree3_changed[0].4, NULL_ID);
    }

    #[test]
    fn test_finalize_on_durable() {
        let store = Arc::new(TestStore::new());
        let mut tree1 = TreeManifest::ephemeral(store.clone());
        tree1
            .insert(repo_path_buf("a1/b1/c1/d1"), make_meta("10"))
            .unwrap();
        tree1
            .insert(repo_path_buf("a1/b2"), make_meta("20"))
            .unwrap();
        tree1
            .insert(repo_path_buf("a2/b2/c2"), make_meta("30"))
            .unwrap();
        let _tree1_changed = tree1.finalize(vec![]).unwrap();

        let mut tree2 = tree1.clone();
        tree2
            .insert(repo_path_buf("a1/b2"), make_meta("40"))
            .unwrap();
        tree2
            .insert(repo_path_buf("a2/b2/c2"), make_meta("60"))
            .unwrap();
        tree2
            .insert(repo_path_buf("a3/b1"), make_meta("50"))
            .unwrap();
        let tree_changed: Vec<_> = tree2.finalize(vec![&tree1]).unwrap().collect();
        assert_eq!(
            tree2.finalize(vec![&tree1]).unwrap().collect::<Vec<_>>(),
            tree_changed,
        );
    }

    #[test]
    fn test_finalize_materialization() {
        let store = Arc::new(TestStore::new());
        let entry_1 = store::Entry::from_elements_hg(vec![
            store_element("foo", "10", store::Flag::Directory),
            store_element("baz", "20", store::Flag::File(FileType::Regular)),
        ]);
        store
            .insert(RepoPath::empty(), hgid("1"), entry_1.to_bytes())
            .unwrap();
        let parent = TreeManifest::durable(store.clone(), hgid("1"));

        let entry_2 = store::Entry::from_elements_hg(vec![
            store_element("foo", "10", store::Flag::Directory),
            store_element("baz", "21", store::Flag::File(FileType::Regular)),
        ]);
        store
            .insert(RepoPath::empty(), hgid("2"), entry_2.to_bytes())
            .unwrap();

        let mut tree = TreeManifest::durable(store.clone(), hgid("2"));

        let _changes: Vec<_> = tree.finalize(vec![&parent]).unwrap().collect();
        // expecting the code to not panic
        // the panic would be caused by materializing link (foo, 10) which
        // doesn't have a store entry
    }

    #[test]
    fn test_cursor_skip_on_root() {
        let tree = TreeManifest::ephemeral(Arc::new(TestStore::new()));
        let mut cursor = tree.root_cursor();
        cursor.skip_subtree();
        match cursor.step() {
            Step::Success => panic!("should have reached the end of the tree"),
            Step::End => {} // success
            Step::Err(error) => panic!("{}", error),
        }
    }

    #[test]
    fn test_cursor_skip() {
        fn step<'a>(cursor: &mut DfsCursor<'a>) {
            match cursor.step() {
                Step::Success => {}
                Step::End => panic!("reached the end too soon"),
                Step::Err(error) => panic!("{}", error),
            }
        }
        let mut tree = TreeManifest::ephemeral(Arc::new(TestStore::new()));
        tree.insert(repo_path_buf("a1"), make_meta("10")).unwrap();
        tree.insert(repo_path_buf("a2/b2"), make_meta("20"))
            .unwrap();
        tree.insert(repo_path_buf("a3"), make_meta("30")).unwrap();

        let mut cursor = tree.root_cursor();
        step(&mut cursor);
        assert_eq!(cursor.path(), RepoPath::empty());
        step(&mut cursor);
        assert_eq!(cursor.path(), RepoPath::from_str("a1").unwrap());
        // Skip leaf
        cursor.skip_subtree();
        step(&mut cursor);
        assert_eq!(cursor.path(), RepoPath::from_str("a2").unwrap());
        // Skip directory
        cursor.skip_subtree();
        step(&mut cursor);
        assert_eq!(cursor.path(), RepoPath::from_str("a3").unwrap());
        // Skip on the element before State::End
        cursor.skip_subtree();
        match cursor.step() {
            Step::Success => panic!("should have reached the end of the tree"),
            Step::End => {} // success
            Step::Err(error) => panic!("{}", error),
        }
    }

    #[test]
    fn test_debug() {
        use std::fmt::Write;

        let store = Arc::new(TestStore::new());
        let mut tree = TreeManifest::ephemeral(store.clone());
        tree.insert(repo_path_buf("a1/b1/c1/d1"), make_meta("10"))
            .unwrap();
        let _hgid = tree.flush().unwrap();

        tree.insert(repo_path_buf("a1/b2"), make_meta("20"))
            .unwrap();
        tree.insert(repo_path_buf("a2/b2/c2"), make_meta("30"))
            .unwrap();

        let mut output = String::new();
        write!(output, "{:?}", tree).unwrap();
        assert_eq!(
            output,
            "Root (Ephemeral)\n\
             | a1 (Ephemeral)\n\
             | | b1 (Durable, 4f75b40350c5a77ea27d3287b371016e2d940bab)\n\
             | | | c1 (Durable, 4495bc0cc4093ed880fe1eb1489635f3cddcf04d)\n\
             | | | | d1 (File, 0000000000000000000000000000000000000010, Regular)\n\
             | | b2 (File, 0000000000000000000000000000000000000020, Regular)\n\
             | a2 (Ephemeral)\n\
             | | b2 (Ephemeral)\n\
             | | | c2 (File, 0000000000000000000000000000000000000030, Regular)\n\
             "
        );
    }

    #[test]
    fn test_compat_subtree_diff() {
        let store = Arc::new(TestStore::new());
        // add ("", 1), ("foo", 11), ("baz", 21), ("foo/bar", 111)
        let root_1_entry = store::Entry::from_elements_hg(vec![
            store_element("foo", "11", store::Flag::Directory),
            store_element("baz", "21", store::Flag::File(FileType::Regular)),
        ]);
        store
            .insert(
                RepoPath::empty(),
                hgid("1"),
                root_1_entry.clone().to_bytes(),
            )
            .unwrap();
        let foo_11_entry = store::Entry::from_elements_hg(vec![store_element(
            "bar",
            "111",
            store::Flag::File(FileType::Regular),
        )]);
        store
            .insert(
                repo_path("foo"),
                hgid("11"),
                foo_11_entry.clone().to_bytes(),
            )
            .unwrap();

        // add ("", 2), ("foo", 12), ("baz", 21), ("foo/bar", 112)
        let root_2_entry = store::Entry::from_elements_hg(vec![
            store_element("foo", "12", store::Flag::Directory),
            store_element("baz", "21", store::Flag::File(FileType::Regular)),
        ]);
        store
            .insert(RepoPath::empty(), hgid("2"), root_2_entry.to_bytes())
            .unwrap();
        let foo_12_entry = store::Entry::from_elements_hg(vec![store_element(
            "bar",
            "112",
            store::Flag::File(FileType::Regular),
        )]);
        store
            .insert(repo_path("foo"), hgid("12"), foo_12_entry.to_bytes())
            .unwrap();

        assert_eq!(
            compat_subtree_diff(
                store.clone(),
                RepoPath::empty(),
                hgid("1"),
                vec![hgid("2")],
                3
            )
            .unwrap(),
            vec![
                (
                    repo_path_buf("foo"),
                    hgid("11"),
                    vec![hgid("12")],
                    foo_11_entry.clone().to_bytes()
                ),
                (
                    RepoPathBuf::new(),
                    hgid("1"),
                    vec![hgid("2")],
                    root_1_entry.clone().to_bytes()
                ),
            ]
        );
        assert_eq!(
            compat_subtree_diff(
                store.clone(),
                RepoPath::empty(),
                hgid("1"),
                vec![hgid("2")],
                1
            )
            .unwrap(),
            vec![(
                RepoPathBuf::new(),
                hgid("1"),
                vec![hgid("2")],
                root_1_entry.clone().to_bytes()
            ),]
        );
        assert_eq!(
            compat_subtree_diff(
                store.clone(),
                repo_path("foo"),
                hgid("11"),
                vec![hgid("12")],
                3
            )
            .unwrap(),
            vec![(
                repo_path_buf("foo"),
                hgid("11"),
                vec![hgid("12")],
                foo_11_entry.clone().to_bytes()
            ),]
        );
        assert_eq!(
            compat_subtree_diff(
                store.clone(),
                RepoPath::empty(),
                hgid("1"),
                vec![hgid("1")],
                3
            )
            .unwrap(),
            vec![]
        );
        assert_eq!(
            compat_subtree_diff(
                store.clone(),
                repo_path("foo"),
                hgid("11"),
                vec![hgid("11")],
                3
            )
            .unwrap(),
            vec![]
        );
        // it is illegal to call compat_subtree_diff with "baz" but we can't validate for it
    }

    #[test]
    fn test_compat_subtree_diff_file_to_directory() {
        let store = Arc::new(TestStore::new());
        // add ("", 1), ("foo", 11)
        let root_1_entry = store::Entry::from_elements_hg(vec![store_element(
            "foo",
            "11",
            store::Flag::File(FileType::Regular),
        )]);
        store
            .insert(
                RepoPath::empty(),
                hgid("1"),
                root_1_entry.clone().to_bytes(),
            )
            .unwrap();

        // add ("", 2), ("foo", 12), ("foo/bar", 121)
        let root_2_entry = store::Entry::from_elements_hg(vec![store_element(
            "foo",
            "12",
            store::Flag::Directory,
        )]);
        store
            .insert(
                RepoPath::empty(),
                hgid("2"),
                root_2_entry.clone().to_bytes(),
            )
            .unwrap();
        let foo_12_entry = store::Entry::from_elements_hg(vec![store_element(
            "bar",
            "121",
            store::Flag::File(FileType::Regular),
        )]);
        store
            .insert(
                repo_path("foo"),
                hgid("12"),
                foo_12_entry.clone().to_bytes(),
            )
            .unwrap();

        assert_eq!(
            compat_subtree_diff(
                store.clone(),
                RepoPath::empty(),
                hgid("2"),
                vec![hgid("1")],
                3
            )
            .unwrap(),
            vec![
                (
                    repo_path_buf("foo"),
                    hgid("12"),
                    vec![],
                    foo_12_entry.clone().to_bytes()
                ),
                (
                    RepoPathBuf::new(),
                    hgid("2"),
                    vec![hgid("1")],
                    root_2_entry.clone().to_bytes()
                ),
            ]
        );
    }

    #[test]
    fn test_list() {
        test_list_format(TreeFormat::Git);
        test_list_format(TreeFormat::Hg);
    }

    fn test_list_format(format: TreeFormat) {
        let mut tree = TreeManifest::ephemeral(Arc::new(TestStore::new().with_format(format)));
        let c1_meta = make_meta("10");
        tree.insert(repo_path_buf("a1/b1/c1"), c1_meta).unwrap();
        let b2_meta = make_meta("20");
        tree.insert(repo_path_buf("a1/b2"), b2_meta).unwrap();
        let _hgid = tree.flush().unwrap();
        let c2_meta = make_meta("30");
        tree.insert(repo_path_buf("a2/b3/c2"), c2_meta).unwrap();
        let b4_meta = make_meta("40");
        tree.insert(repo_path_buf("a2/b4"), b4_meta).unwrap();

        assert_eq!(tree.list(repo_path("not_found")).unwrap(), List::NotFound);
        assert_eq!(tree.list(repo_path("a1/b1/c1")).unwrap(), List::File);
        assert_eq!(
            tree.list(repo_path("a1/b1")).unwrap(),
            List::Directory(vec![(
                path_component_buf("c1"),
                FsNodeMetadata::File(c1_meta)
            )]),
        );
        assert_eq!(
            tree.list(repo_path("a1")).unwrap(),
            List::Directory(vec![
                (
                    path_component_buf("b1"),
                    tree.get(repo_path("a1/b1")).unwrap().unwrap()
                ),
                (path_component_buf("b2"), FsNodeMetadata::File(b2_meta)),
            ]),
        );
        assert_eq!(tree.list(repo_path("a2/b3/c2")).unwrap(), List::File);
        assert_eq!(
            tree.list(repo_path("a2/b3")).unwrap(),
            List::Directory(vec![(
                path_component_buf("c2"),
                FsNodeMetadata::File(c2_meta)
            )]),
        );
        assert_eq!(
            tree.list(repo_path("a2")).unwrap(),
            List::Directory(vec![
                (path_component_buf("b3"), FsNodeMetadata::Directory(None)),
                (path_component_buf("b4"), FsNodeMetadata::File(b4_meta)),
            ]),
        );
        assert_eq!(
            tree.list(RepoPath::empty()).unwrap(),
            List::Directory(vec![
                (
                    path_component_buf("a1"),
                    tree.get(repo_path("a1")).unwrap().unwrap()
                ),
                (path_component_buf("a2"), FsNodeMetadata::Directory(None)),
            ]),
        );
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::ops::Deref;
use std::sync::Arc;

use anyhow::anyhow;
use anyhow::bail;
use anyhow::Context;
use anyhow::Result;
use manifest::File;
use manifest::FileMetadata;
use manifest::FsNodeMetadata;
use once_cell::sync::OnceCell;
use pathmatcher::DirectoryMatch;
use pathmatcher::Matcher;
use types::HgId;
use types::Key;
use types::PathComponentBuf;
use types::RepoPath;
use types::RepoPathBuf;

use crate::store;
use crate::store::InnerStore;

// Allows sending link between threads, but disallows general copying.
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Link {
    inner: Arc<LinkData>,
}

impl Deref for Link {
    type Target = LinkData;
    fn deref(&self) -> &LinkData {
        self.inner.as_ref()
    }
}

impl AsRef<LinkData> for Link {
    fn as_ref(&self) -> &LinkData {
        self.inner.as_ref()
    }
}

impl Clone for Link {
    fn clone(&self) -> Self {
        // Most code should not be aware of the fact that Link can be cloned as an Arc, so for the
        // default clone implementation do a deep clone. thread_copy() should be used for explicit
        // cases that need a shallow copy.
        Link::new(self.inner.as_ref().clone())
    }
}

/// `Link` describes the type of nodes that tree manifest operates on.
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub enum LinkData {
    /// `Leaf` nodes store FileMetadata. They are terminal nodes and don't have any other
    /// information.
    Leaf(FileMetadata),
    /// `Ephemeral` nodes are inner nodes that have not been committed to storage. They are only
    /// available in memory. They need to be persisted to be available in future. They are the
    /// mutable type of an inner node. They store the contents of a directory that has been
    /// modified.
    Ephemeral(BTreeMap<PathComponentBuf, Link>),
    /// `Durable` nodes are inner nodes that come from storage. Their contents can be
    /// shared between multiple instances of Tree. They are lazily evaluated. Their children
    /// list will be read from storage only when it is accessed.
    Durable(Arc<DurableEntry>),
}
pub use self::LinkData::*;

// TODO: Use Vec instead of BTreeMap
/// The inner structure of a durable link.
#[derive(Debug)]
pub struct DurableEntry {
    pub hgid: HgId,
    pub links: OnceCell<BTreeMap<PathComponentBuf, Link>>,
}

impl Link {
    pub fn new(link: LinkData) -> Self {
        Link {
            inner: Arc::new(link),
        }
    }

    pub fn durable(hgid: HgId) -> Link {
        Link::new(LinkData::Durable(Arc::new(DurableEntry::new(hgid))))
    }

    pub fn ephemeral() -> Link {
        Link::new(LinkData::Ephemeral(BTreeMap::new()))
    }

    pub fn leaf(metadata: FileMetadata) -> Link {
        Link::new(LinkData::Leaf(metadata))
    }

    pub fn mut_ephemeral_links(
        &mut self,
        store: &InnerStore,
        parent: &RepoPath,
    ) -> Result<&mut BTreeMap<PathComponentBuf, Link>> {
        self.as_mut_ref()?.mut_ephemeral_links(store, parent)
    }

    pub fn to_fs_node(&self) -> FsNodeMetadata {
        match self.as_ref() {
            Leaf(metadata) => FsNodeMetadata::File(*metadata),
            Ephemeral(_) => FsNodeMetadata::Directory(None),
            Durable(durable) => FsNodeMetadata::Directory(Some(durable.hgid)),
        }
    }

    /// Create a file record for a `Link`, failing if the link
    /// refers to a directory rather than a file.
    pub fn to_file(&self, path: RepoPathBuf) -> Option<File> {
        match self.as_ref() {
            Leaf(metadata) => Some(File::new(path, *metadata)),
            _ => None,
        }
    }

    pub fn matches(&self, matcher: &impl Matcher, path: &RepoPath) -> Result<bool> {
        match self.as_ref() {
            Leaf(_) => matcher.matches_file(path),
            Durable(_) | Ephemeral(_) => {
                Ok(matcher.matches_directory(path)? != DirectoryMatch::Nothing)
            }
        }
    }

    pub fn thread_copy(&self) -> Self {
        Link {
            inner: self.inner.clone(),
        }
    }

    pub fn as_mut_ref(&mut self) -> Result<&mut LinkData> {
        // This introduces an unsual mutability pattern where we allow mutations as long as there
        // is only one copy of the Link's Arc. That one copy will always be the parent directory.
        // In normal treemanifest operations, these Links are never shared beween trees (and in
        // fact the only way to copy the Arc is through the thread_copy function) so it will always
        // be the case that there is only one copy.
        //
        // The one exception, and the reason we use Arc at all, is during tree traversals. In that
        // case we pass copies of the Arc to other threads for parallel traversals. Therefore we
        // cannot use as_mut_ref() while traversing the tree.
        //
        // Normally we'd use RwLock so the compiler could enforce this, but having a RwLock on
        // every Link would be expensive and tree reads are a critical hotpath. Using Arc like this
        // gives us zero-cost reads and standard rust mutable-reference safety, as long as we don't
        // copy the Arc.

        Arc::get_mut(&mut self.inner).ok_or_else(|| {
            anyhow!("cannot mutate tree manifest link if there are multiple readers")
        })
    }
}

impl LinkData {
    pub fn mut_ephemeral_links(
        &mut self,
        store: &InnerStore,
        parent: &RepoPath,
    ) -> Result<&mut BTreeMap<PathComponentBuf, Link>> {
        loop {
            match self {
                Leaf(_) => bail!("Path {} is a file but a directory was expected.", parent),
                Ephemeral(ref mut links) => return Ok(links),
                Durable(ref entry) => {
                    let durable_links = entry.materialize_links(store, parent)?;
                    *self = Ephemeral(durable_links.clone());
                }
            };
        }
    }
}

impl DurableEntry {
    pub fn new(hgid: HgId) -> Self {
        DurableEntry {
            hgid,
            links: OnceCell::new(),
        }
    }

    pub fn materialize_links(
        &self,
        store: &InnerStore,
        path: &RepoPath,
    ) -> Result<&BTreeMap<PathComponentBuf, Link>> {
        self.links.get_or_try_init(|| {
            let entry = store
                .get_entry(path, self.hgid)
                .with_context(|| format!("failed fetching from store ({}, {})", path, self.hgid))?;
            let mut links = BTreeMap::new();
            for element_result in entry.elements() {
                let element = element_result.with_context(|| {
                    format!(
                        "failed to deserialize manifest entry {:?} for ({}, {})",
                        entry, path, self.hgid
                    )
                })?;
                let link = match element.flag {
                    store::Flag::File(file_type) => {
                        Link::leaf(FileMetadata::new(element.hgid, file_type))
                    }
                    store::Flag::Directory => Link::durable(element.hgid),
                };
                links.insert(element.component, link);
            }
            Ok(links)
        })
    }
}

// `PartialEq` can't be derived because `fallible::Error` does not implement `PartialEq`.
// It should also be noted that `self.links.get() != self.links.get()` can evaluate to true when
// `self.links` are being instantiated.
#[cfg(test)]
impl PartialEq for DurableEntry {
    fn eq(&self, other: &DurableEntry) -> bool {
        if self.hgid != other.hgid {
            return false;
        }
        match (self.links.get(), other.links.get()) {
            (None, None) => true,
            (Some(a), Some(b)) => a == b,
            _ => false,
        }
    }
}

/// A directory (inner node) encountered during a tree traversal.
///
/// The directory may have a manifest hgid if it is unmodified from its
/// state on disk. If the directory has in-memory modifications that have not
/// been persisted to disk, it will not have an hgid.
#[derive(Clone, Debug)]
pub struct DirLink {
    pub path: RepoPathBuf,
    pub link: Link,
}

impl DirLink {
    /// Create a directory record for a `Link`, failing if the link
    /// refers to a file rather than a directory.
    pub fn from_link(link: &Link, path: RepoPathBuf) -> Option<Self> {
        if let Leaf(_) = link.as_ref() {
            return None;
        }
        Some(DirLink {
            path,
            link: link.thread_copy(),
        })
    }

    /// Same as `from_link`, but set the directory's path to the empty
    /// path, making this method only useful for the root of the tree.
    pub fn from_root(link: &Link) -> Option<Self> {
        Self::from_link(link, RepoPathBuf::new())
    }

    pub fn hgid(&self) -> Option<HgId> {
        match self.link.as_ref() {
            Leaf(_) | Ephemeral(_) => None,
            Durable(entry) => Some(entry.hgid),
        }
    }

    /// List the contents of this directory.
    ///
    /// Returns two sorted vectors of files and directories contained
    /// in this directory.
    ///
    /// This operation may perform I/O to load the tree entry from the store
    /// if it is not already in memory. Depending on the store implementation,
    /// this may involve an expensive network request if the required data is
    /// not available locally. As such, algorithms that require fast access to
    /// this data should take care to ensure that this content is present
    /// locally before calling this method.
    pub fn list(&self, store: &InnerStore) -> Result<(Vec<File>, Vec<DirLink>)> {
        let mut files = Vec::new();
        let mut dirs = Vec::new();

        let links = match self.link.as_ref() {
            Leaf(_) => panic!("programming error: directory cannot be a leaf node"),
            Ephemeral(ref links) => links,
            Durable(entry) => entry.materialize_links(store, &self.path)?,
        };

        for (name, link) in links {
            let mut path = self.path.clone();
            path.push(name.as_ref());
            match link.as_ref() {
                Leaf(_) => files.push(link.to_file(path).expect("leaf node must be a valid file")),
                Ephemeral(_) | Durable(_) => dirs.push(
                    DirLink::from_link(link, path).expect("inner node must be a valid directory"),
                ),
            }
        }

        Ok((files, dirs))
    }

    /// Create a `Key` (path/hgid pair) corresponding to this directory. Keys are used
    /// by the Eden API to fetch data from the server, making this representation useful
    /// for interacting with Mercurial's data fetching code.
    pub fn key(&self) -> Option<Key> {
        Some(Key::new(self.path.clone(), self.hgid().clone()?))
    }
}

impl Eq for DirLink {}

impl PartialEq for DirLink {
    fn eq(&self, other: &Self) -> bool {
        self.path == other.path && self.hgid() == other.hgid()
    }
}

impl Ord for DirLink {
    fn cmp(&self, other: &Self) -> Ordering {
        match self.path.cmp(&other.path) {
            Ordering::Equal => self.hgid().cmp(&other.hgid()),
            ord => ord,
        }
    }
}

impl PartialOrd for DirLink {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

#[cfg(test)]
mod tests {
    use manifest::testutil::*;
    use types::testutil::*;

    use super::*;
    use crate::testutil::*;

    #[test]
    fn test_file_from_link() {
        // Leaf link should result in a file.
        let meta = make_meta("a");
        let path = repo_path_buf("test/leaf");

        let leaf = Link::leaf(meta.clone());
        let file = leaf.to_file(path.clone()).unwrap();

        let expected = File {
            path: path.clone(),
            meta,
        };
        assert_eq!(file, expected);

        // Attempting to use a directory link should fail.
        let ephemeral = Link::ephemeral();
        let _file = ephemeral.to_file(path.clone());

        // Durable link should result in a directory.
        let durable = Link::durable(hgid("a"));
        let file = durable.to_file(path.clone());
        assert!(file.is_none());
    }

    #[test]
    fn test_directory_from_link() {
        let meta = make_meta("a");
        let path = repo_path_buf("test/leaf");

        let ephemeral = Link::ephemeral();
        let dir = DirLink::from_link(&ephemeral, path.clone()).unwrap();
        let expected = DirLink {
            path: path.clone(),
            link: ephemeral,
        };
        assert_eq!(dir, expected);

        let hash = hgid("b");
        let durable = Link::durable(hash);
        let dir = DirLink::from_link(&durable, path.clone()).unwrap();
        let expected = DirLink {
            path: path.clone(),
            link: durable,
        };
        assert_eq!(dir, expected);

        // If the Link is actually a file, we should get None.
        let leaf = Link::leaf(meta.clone());
        let dir = DirLink::from_link(&leaf, path.clone());
        assert!(dir.is_none());
    }

    #[test]
    fn test_list_directory() -> Result<()> {
        let store = Arc::new(TestStore::new());
        let tree = make_tree_manifest(store, &[("a", "1"), ("b/f", "2"), ("c", "3"), ("d/f", "4")]);
        let dir = DirLink::from_root(&tree.root).unwrap();
        let (files, dirs) = dir.list(&tree.store)?;

        let file_names = files.into_iter().map(|f| f.path).collect::<Vec<_>>();
        let dir_names = dirs.into_iter().map(|d| d.path).collect::<Vec<_>>();

        assert_eq!(file_names, vec![repo_path_buf("a"), repo_path_buf("c")]);
        assert_eq!(dir_names, vec![repo_path_buf("b"), repo_path_buf("d")]);

        Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::sync::Arc;

use anyhow::Result;
use manifest::FsNodeMetadata::Directory;
use manifest::FsNodeMetadata::File;
use manifest::Manifest;
use parking_lot::RwLock;
use pathmatcher::DirectoryMatch;
use pathmatcher::Matcher;
use types::RepoPath;

use crate::TreeManifest;

pub struct ManifestMatcher {
    manifest: Arc<RwLock<TreeManifest>>,
    case_sensitive: bool,
}

impl ManifestMatcher {
    pub fn new(manifest: Arc<RwLock<TreeManifest>>, case_sensitive: bool) -> Self {
        ManifestMatcher {
            manifest,
            case_sensitive,
        }
    }
}

impl Matcher for ManifestMatcher {
    fn matches_directory(&self, path: &RepoPath) -> Result<DirectoryMatch> {
        let manifest = self.manifest.read();
        let result = if self.case_sensitive {
            manifest.get(path)?
        } else {
            manifest.get_ignore_case(path)?
        };
        Ok(match result {
            Some(File(_)) => DirectoryMatch::Nothing,
            Some(Directory(_)) => DirectoryMatch::ShouldTraverse,
            None => DirectoryMatch::Nothing,
        })
    }

    fn matches_file(&self, path: &RepoPath) -> Result<bool> {
        let manifest = self.manifest.read();
        let result = if self.case_sensitive {
            manifest.get(path)?
        } else {
            manifest.get_ignore_case(path)?
        };
        Ok(match result {
            Some(File(_)) => true,
            Some(Directory(_)) => false,
            None => false,
        })
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

//! Functions to compare path components.
//! Decide the ordering of items in a tree.

use std::cmp::Ordering;

use storemodel::TreeFormat;
use types::PathComponent;

use crate::Flag;

type NameCmpFunc = fn(&PathComponent, Flag, &PathComponent, Flag) -> Ordering;

pub(crate) fn get_namecmp_func(format: TreeFormat) -> NameCmpFunc {
    match format {
        TreeFormat::Git => namecmp_git,
        TreeFormat::Hg => namecmp_hg,
    }
}

pub(crate) fn namecmp_hg(
    name1: &PathComponent,
    flag1: Flag,
    name2: &PathComponent,
    flag2: Flag,
) -> Ordering {
    (name1, flag1).cmp(&(name2, flag2))
}

/// Compare names for git.
/// Git treats directory names differently by appending `/` to them.
pub(crate) fn namecmp_git(
    name1: &PathComponent,
    flag1: Flag,
    name2: &PathComponent,
    flag2: Flag,
) -> Ordering {
    let name1 = name1.as_str().as_bytes();
    let name2 = name2.as_str().as_bytes();

    // This is basically base_name_compare() authored by Linus in
    // https://github.com/git/git/commit/958ba6c96eb58b359c855c9d07e3e45072f0503e
    let len = name1.len().min(name2.len());
    let cmp = name1[..len].cmp(&name2[..len]);
    if cmp != Ordering::Equal {
        return cmp;
    }

    let end = |flag| if flag == Flag::Directory { &b'/' } else { &0 };
    let end1 = name1.get(len).unwrap_or_else(|| end(flag1));
    let end2 = name2.get(len).unwrap_or_else(|| end(flag2));

    end1.cmp(end2)
}

#[cfg(test)]
pub(crate) mod tests {
    use manifest::FileType;
    use types::PathComponentBuf;

    use super::*;

    #[test]
    fn test_namecmp_git() {
        let names = ["a", "b", "b-", "b0", "b/", "b-/", "b0/", "c"];
        for s1 in names.iter() {
            for s2 in names.iter() {
                check_namecmp_git(s1, s2);
            }
        }
    }

    /// s1 and s2 might end with '/' to indicate they are trees.
    fn check_namecmp_git(s1: &str, s2: &str) {
        let expected = s1.cmp(s2);

        let (name1, flag1) = get_name_flag(s1);
        let (name2, flag2) = get_name_flag(s2);

        let cmp = get_namecmp_func(TreeFormat::Git);
        let actual = cmp(&name1, flag1, &name2, flag2);
        assert_eq!(actual, expected);
    }

    /// Convenient function to get "PathComponentBuf" and "Flag" from a
    /// string that might ends with '/' indicating a directory.
    pub(crate) fn get_name_flag(name: &str) -> (PathComponentBuf, Flag) {
        let flag = if name.ends_with('/') {
            Flag::Directory
        } else {
            Flag::File(FileType::Regular)
        };
        let name = PathComponentBuf::from_string(name.trim_end_matches('/').to_string()).unwrap();
        (name, flag)
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::cmp::Ordering;
use std::sync::Arc;

use anyhow::format_err;
use anyhow::Result;
use manifest::FileMetadata;
use manifest::FileType;
use manifest::FsNodeMetadata;
use minibytes::Bytes;
use storemodel::TreeFormat;
pub use storemodel::TreeStore;
use types::HgId;
use types::Key;
use types::PathComponent;
use types::PathComponentBuf;
use types::RepoPath;

#[derive(Clone)]
pub struct InnerStore {
    tree_store: Arc<dyn TreeStore + Send + Sync>,
}

impl InnerStore {
    pub fn new(tree_store: Arc<dyn TreeStore + Send + Sync>) -> Self {
        InnerStore { tree_store }
    }

    pub fn format(&self) -> TreeFormat {
        self.tree_store.format()
    }

    pub fn get_entry(&self, path: &RepoPath, hgid: HgId) -> Result<Entry> {
        tracing::debug_span!(
            "tree::store::get",
            id = AsRef::<str>::as_ref(&hgid.to_hex())
        )
        .in_scope(|| {
            let bytes = self.tree_store.get(path, hgid)?;
            Ok(Entry(bytes, self.tree_store.format()))
        })
    }

    pub fn insert_entry(&self, path: &RepoPath, hgid: HgId, entry: Entry) -> Result<()> {
        tracing::debug_span!(
            "tree::store::insert",
            path = path.as_str(),
            id = AsRef::<str>::as_ref(&hgid.to_hex())
        )
        .in_scope(|| self.tree_store.insert(path, hgid, entry.0))
    }

    pub fn prefetch(&self, keys: impl IntoIterator<Item = Key>) -> Result<()> {
        let keys: Vec<Key> = keys.into_iter().collect();
        tracing::debug_span!(
            "tree::store::prefetch",
            ids = {
                let ids: Vec<String> = keys.iter().map(|k| k.hgid.to_hex()).collect();
                &AsRef::<str>::as_ref(&ids.join(" "))
            }
        )
        .in_scope(|| self.tree_store.prefetch(keys))
    }
}

/// The `Entry` is the data that is stored on disk. It should be seen as opaque to whether it
/// represents serialized or deserialized data. It is the object that performs the bytes to
/// business object transformations. It provides method for interacting with parsed data.
///
/// The ABNF specification for the current serialization is:
/// Entry         = 1*( Element LF )
/// Element       = PathComponent %x00 HgId [ Flag ]
/// Flag          = %s"x" / %s"l" / %s"t"
/// PathComponent = 1*( %x01-%x09 / %x0B-%xFF )
/// HgId          = 40HEXDIG
///
/// In this case an `Entry` is equivalent to the contents of a directory. The elements of the
/// directory are described by `Element`. `Entry` is a list of serialized `Element`s that are
/// separated by `\n`. An `Element` will always have a name (`PathComponent`) and a hash (`HgId`).
/// `Elements` may be different types of files or they can be directories. The type of element is
/// described by the flag or the absence of the flag. When the flag is missing we have a regular
/// file, the various flag options are: `x` for executable, `l` for symlink and `d` for directory.
/// It should be noted that Nodes are represented in their hex format rather than a straight up
/// binary format so they are 40 characters long rather than 20 bytes.
/// Check the documentation of the `PathComponent` struct for more details about it's
/// representation. For this serialization format it is important that they don't contain
/// `\0` or `\n`.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Entry(pub minibytes::Bytes, pub TreeFormat);

pub struct EntryMut(Vec<u8>, TreeFormat);

/// The `Element` is a parsed element of a directory. Directory elements are either files either
/// direcotries. The type of element is signaled by `Flag`.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Element {
    pub component: PathComponentBuf,
    pub hgid: HgId,
    pub flag: Flag,
}

/// Used to signal the type of element in a directory: file or directory.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Flag {
    File(FileType),
    Directory,
}

impl Entry {
    /// Returns an iterator over the elements that the current `Entry` contains. This is the
    /// primary method of inspection for an `Entry`.
    pub fn elements<'a>(&'a self) -> Elements<'a> {
        Elements::from_byte_slice(&self.0, self.1)
    }

    /// The primary builder of an Entry, from a list of `Element`.
    pub fn from_elements(mut elements: Vec<Element>, format: TreeFormat) -> Entry {
        let cmp = crate::namecmp::get_namecmp_func(format);
        elements.sort_unstable_by(|a, b| cmp(&a.component, a.flag, &b.component, b.flag));
        let mut underlying = Vec::new();
        match format {
            TreeFormat::Hg => {
                for element in elements.into_iter() {
                    underlying.extend(element.to_byte_vec_hg());
                    underlying.extend(b"\n");
                }
            }
            TreeFormat::Git => {
                for element in elements.into_iter() {
                    underlying.extend(element.to_byte_vec_git());
                }
            }
        }
        Entry(underlying.into(), format)
    }

    // used in tests, finalize and subtree_diff
    pub fn to_bytes(self) -> Bytes {
        self.0
    }
}

impl EntryMut {
    /// Constructs an empty `Entry`. It is not valid to save an empty `Entry`.
    pub fn new(format: TreeFormat) -> Self {
        EntryMut(Vec::new(), format)
    }

    /// Adds an element to the list of elements represented by this `Entry`.
    /// It is expected that elements are added sorted by paths.
    pub fn add_element_hg(&mut self, element: Element) {
        self.0.extend(element.to_byte_vec_hg());
        self.0.extend(b"\n");
    }

    pub fn freeze(self) -> Entry {
        Entry(self.0.into(), self.1)
    }
}

impl AsRef<[u8]> for Entry {
    fn as_ref(&self) -> &[u8] {
        &self.0
    }
}

pub struct Elements<'a> {
    byte_slice: &'a [u8],
    position: usize,
    format: TreeFormat,
}

impl<'a> Elements<'a> {
    /// Constructs `Elements` from raw byte slice.
    pub(crate) fn from_byte_slice(byte_slice: &'a [u8], format: TreeFormat) -> Self {
        Elements {
            byte_slice,
            position: 0,
            format,
        }
    }

    fn next_hg(&mut self) -> Option<Result<Element>> {
        if self.position >= self.byte_slice.len() {
            return None;
        }
        let end = match self.byte_slice[self.position..]
            .iter()
            .position(|&byte| byte == b'\n')
        {
            None => {
                return Some(Err(format_err!(
                    "failed to deserialize tree manifest entry, missing line feed\n{:?}",
                    String::from_utf8_lossy(self.byte_slice)
                )));
            }
            Some(delta) => self.position + delta,
        };
        let result = Element::from_byte_slice_hg(&self.byte_slice[self.position..end]);
        self.position = end + 1;
        Some(result)
    }

    fn next_git(&mut self) -> Option<Result<Element>> {
        let slice = match self.byte_slice.get(self.position..) {
            None => return None,
            Some(s) if s.is_empty() => return None,
            Some(s) => s,
        };

        let (mode_len, name_len) = find_git_entry_positions(slice)?;

        let flag = match parse_git_mode(&slice[..mode_len]) {
            Ok(flag) => flag,
            Err(e) => return Some(Err(e)),
        };

        let mut offset = mode_len + 1;
        let name = &slice[offset..offset + name_len];
        let component = match PathComponent::from_utf8(name) {
            Ok(p) => p.to_owned(),
            Err(e) => return Some(Err(e.into())),
        };

        offset += name_len + 1;
        let hgid = if let Some(id_slice) = slice.get(offset..offset + HgId::len()) {
            HgId::from_slice(id_slice).expect("id_slice has the right length")
        } else {
            return Some(Err(format_err!("SHA1 is incomplete")));
        };

        offset += HgId::len();
        self.position += offset;

        let element = Element {
            component,
            hgid,
            flag,
        };
        Some(Ok(element))
    }

    /// Look up an item.
    /// This can be faster than checking `next()` entries if only called once.
    pub fn lookup(&self, name: &PathComponent) -> Result<Option<(HgId, Flag)>> {
        match self.format {
            TreeFormat::Hg => self.lookup_hg(name),
            TreeFormat::Git => self.lookup_git(name),
        }
    }

    fn lookup_hg(&self, name: &PathComponent) -> Result<Option<(HgId, Flag)>> {
        // NAME '\0' HEX_SHA1 MODE '\n'
        let mut slice: &[u8] = self.byte_slice;
        let name = {
            let name = name.as_byte_slice();
            let mut buf = Vec::with_capacity(name.len() + 1);
            buf.extend_from_slice(name);
            buf.push(b'\0');
            buf
        };
        while slice.len() >= name.len() {
            match slice[..name.len()].cmp(name.as_slice()) {
                Ordering::Less => {
                    // Check the next entry.
                    match slice.iter().skip(HgId::hex_len()).position(|&x| x == b'\n') {
                        Some(position) => {
                            slice = &slice[position + HgId::hex_len() + 1..];
                            continue;
                        }
                        None => break,
                    };
                }
                Ordering::Equal => {
                    let hex_start = name.len();
                    let hex_end = hex_start + HgId::hex_len();
                    let hex_slice = match slice.get(hex_start..hex_end) {
                        None => break,
                        Some(slice) => slice,
                    };
                    let hgid = HgId::from_hex(hex_slice)?;
                    let flag = parse_hg_flag(slice.get(hex_end))?;
                    return Ok(Some((hgid, flag)));
                }
                Ordering::Greater => break,
            }
        }
        Ok(None)
    }

    fn lookup_git(&self, name: &PathComponent) -> Result<Option<(HgId, Flag)>> {
        let mut slice: &[u8] = self.byte_slice;
        let name: &[u8] = name.as_byte_slice();
        while !slice.is_empty() {
            let (mode_len, name_len) = match find_git_entry_positions(slice) {
                Some(positions) => positions,
                None => return Ok(None),
            };
            let name_start = mode_len + 1;
            let name_end = name_start + name_len;
            let candidate = match slice.get(name_start..name_end) {
                Some(name) => name,
                None => break,
            };
            match candidate.cmp(name) {
                Ordering::Less => {}
                Ordering::Equal => {
                    let flag = parse_git_mode(&slice[..mode_len])?;
                    let id_start = name_end + 1;
                    let id_end = id_start + HgId::len();
                    let hgid = if let Some(id_slice) = slice.get(id_start..id_end) {
                        HgId::from_slice(id_slice).expect("id_slice has the right length")
                    } else {
                        return Err(format_err!("SHA1 is incomplete"));
                    };
                    return Ok(Some((hgid, flag)));
                }
                Ordering::Greater => {
                    // Directory names are tricky. See the `namecmp` module.
                    // Here we don't borther figuring out whehter it's a
                    // directory or not, just keep looking for a few more
                    // entires.
                    let len = candidate.len().min(name.len());
                    if candidate[..len] > name[..len] {
                        break;
                    }
                }
            }
            slice = match slice.get(name_end + 1 + HgId::len()..) {
                Some(slice) => slice,
                None => break,
            };
        }
        Ok(None)
    }
}

impl<'a> Iterator for Elements<'a> {
    type Item = Result<Element>;

    fn next(&mut self) -> Option<Self::Item> {
        let item = match self.format {
            TreeFormat::Hg => self.next_hg(),
            TreeFormat::Git => self.next_git(),
        };

        if cfg!(debug_assertions) {
            if let Some(Ok(item)) = &item {
                let lookup = self.lookup(&item.component).unwrap();
                assert_eq!(
                    Some((item.hgid, item.flag)),
                    lookup,
                    "when lookup '{}'",
                    item.component.as_str()
                );
            }
        }

        item
    }
}

impl TryFrom<Entry> for manifest::List {
    type Error = anyhow::Error;

    fn try_from(v: Entry) -> Result<Self> {
        let mut entries = Vec::new();
        for entry in v.elements() {
            let entry = entry?;
            entries.push((
                entry.component,
                match entry.flag {
                    Flag::Directory => FsNodeMetadata::Directory(Some(entry.hgid)),
                    Flag::File(file_type) => {
                        FsNodeMetadata::File(FileMetadata::new(entry.hgid, file_type))
                    }
                },
            ))
        }
        Ok(manifest::List::Directory(entries))
    }
}

impl Element {
    pub fn new(component: PathComponentBuf, hgid: HgId, flag: Flag) -> Element {
        Element {
            component,
            hgid,
            flag,
        }
    }

    fn from_byte_slice_hg(byte_slice: &[u8]) -> Result<Element> {
        let path_len = match byte_slice.iter().position(|&x| x == b'\0') {
            Some(position) => position,
            None => return Err(format_err!("did not find path delimiter")),
        };
        let component = PathComponent::from_utf8(&byte_slice[..path_len])?.to_owned();
        if path_len + HgId::hex_len() > byte_slice.len() {
            return Err(format_err!("hgid length is shorter than expected"));
        }
        if byte_slice.len() > path_len + HgId::hex_len() + 2 {
            return Err(format_err!("entry longer than expected"));
        }
        let hex_slice = &byte_slice[path_len + 1..path_len + HgId::hex_len() + 1];
        let hgid = HgId::from_hex(hex_slice)?;
        let flag = parse_hg_flag(byte_slice.get(path_len + HgId::hex_len() + 1))?;
        let element = Element {
            component,
            hgid,
            flag,
        };
        Ok(element)
    }

    fn to_byte_vec_hg(&self) -> Vec<u8> {
        let component = self.component.as_byte_slice();
        // TODO: benchmark taking a buffer as a parameter
        // We may not use the last byte but it doesn't hurt to allocate
        let mut buffer = Vec::with_capacity(component.len() + HgId::hex_len() + 2);
        buffer.extend_from_slice(component);
        buffer.push(0);
        buffer.extend_from_slice(self.hgid.to_hex().as_ref());
        let flag = match self.flag {
            Flag::File(FileType::Regular) => None,
            Flag::File(FileType::Executable) => Some(b'x'),
            Flag::File(FileType::Symlink) => Some(b'l'),
            Flag::Directory => Some(b't'),
            Flag::File(FileType::GitSubmodule) => {
                panic!("bug: hg tree does not support git submodule")
            }
        };
        if let Some(byte) = flag {
            buffer.push(byte);
        }
        buffer
    }

    fn to_byte_vec_git(&self) -> Vec<u8> {
        let mode: &[u8] = match self.flag {
            Flag::File(FileType::Regular) => b"100644",
            Flag::File(FileType::Executable) => b"100755",
            Flag::File(FileType::Symlink) => b"120000",
            Flag::Directory => b"40000",
            Flag::File(FileType::GitSubmodule) => b"160000",
        };
        let component = self.component.as_byte_slice();
        let mut buffer = Vec::with_capacity(mode.len() + component.len() + HgId::len() + 2);
        buffer.extend_from_slice(mode);
        buffer.push(b' ');
        buffer.extend_from_slice(component);
        buffer.push(b'\0');
        buffer.extend_from_slice(self.hgid.as_ref());
        buffer
    }
}

/// Find the byte offsets used in a git entry.
/// Return (mode_len, name_len).
fn find_git_entry_positions(slice: &[u8]) -> Option<(usize, usize)> {
    // MODE ' '       NAME          '\0'                     BIN_SHA1
    //      ^         ^             ^                        ^
    //      mode_len  mode_len + 1  mode_len + 1 + name_len  .. + 1
    let mode_len = match slice.iter().position(|&x| x == b' ') {
        Some(position) => position,
        None => return None,
    };
    let name_len = match slice[mode_len..].iter().position(|&x| x == b'\0') {
        Some(position) if position > 1 => position - 1,
        _ => return None,
    };
    Some((mode_len, name_len))
}

/// Convert the git mode (ex. b"100644") to `Flag`.
fn parse_git_mode(mode: &[u8]) -> Result<Flag> {
    let flag = match mode {
        b"40000" => Flag::Directory,
        // 100664 is non-standard but present in old repos.
        // See https://github.com/git/git/commit/42ea9cb286423c949d42ad33823a5221182f84bf
        b"100644" | b"100664" => Flag::File(FileType::Regular),
        b"100755" => Flag::File(FileType::Executable),
        b"120000" => Flag::File(FileType::Symlink),
        b"160000" => Flag::File(FileType::GitSubmodule),
        s => {
            return Err(format_err!(
                "unknown or unsupported mode in git tree ({})",
                String::from_utf8_lossy(s)
            ));
        }
    };
    Ok(flag)
}

/// Convert hg flag to `Flag`.
fn parse_hg_flag(flag_byte: Option<&u8>) -> Result<Flag> {
    let flag = match flag_byte {
        Some(b'\n') | None => Flag::File(FileType::Regular),
        Some(b'x') => Flag::File(FileType::Executable),
        Some(b'l') => Flag::File(FileType::Symlink),
        Some(b't') => Flag::Directory,
        Some(bad_flag) => return Err(format_err!("invalid flag {}", bad_flag)),
    };
    Ok(flag)
}

#[cfg(test)]
mod tests {
    use quickcheck::quickcheck;
    use types::testutil::*;

    use super::*;

    #[test]
    fn test_element_from_byte_slice_hg() {
        let mut buffer = vec![];
        let path = PathComponent::from_str("foo").unwrap();
        let hgid = hgid("123");
        assert!(Element::from_byte_slice_hg(&buffer).is_err());
        buffer.extend_from_slice(path.as_byte_slice());
        assert!(Element::from_byte_slice_hg(&buffer).is_err());
        buffer.push(b'\0');
        assert!(Element::from_byte_slice_hg(&buffer).is_err());
        buffer.extend_from_slice(hgid.to_hex().as_ref());
        assert_eq!(
            Element::from_byte_slice_hg(&buffer).unwrap(),
            Element::new(path.to_owned(), hgid, Flag::File(FileType::Regular))
        );

        buffer.push(b'x');
        assert_eq!(
            Element::from_byte_slice_hg(&buffer).unwrap(),
            Element::new(path.to_owned(), hgid, Flag::File(FileType::Executable))
        );

        *buffer.last_mut().unwrap() = b'l';
        assert_eq!(
            Element::from_byte_slice_hg(&buffer).unwrap(),
            Element::new(path.to_owned(), hgid, Flag::File(FileType::Symlink))
        );

        *buffer.last_mut().unwrap() = b't';
        assert_eq!(
            Element::from_byte_slice_hg(&buffer).unwrap(),
            Element::new(path.to_owned(), hgid, Flag::Directory)
        );

        *buffer.last_mut().unwrap() = b's';
        assert!(Element::from_byte_slice_hg(&buffer).is_err());

        *buffer.last_mut().unwrap() = b'x';
        buffer.push(b'\0');
        assert!(Element::from_byte_slice_hg(&buffer).is_err());
    }

    #[test]
    fn test_deserialize_git_tree() {
        // Tree data generated by:
        //
        // mkdir -p foo && cd foo
        // ( git init; touch normal; mkdir dir; touch dir/1; ln -s normal symlink; touch exe; chmod +x exe; git add .; git commit -m c; ) >/dev/null
        // TREE=$(git cat-file -p HEAD | grep tree | sed 's/.* //')
        // git cat-file tree $TREE
        let data = b"40000 dir\x00\x8d\xc8w\xa9\x98\xd8\xc6\x1f\x90\x0e\x8bN\xe9\xb5\x01\xfa\n\x03\x93X100755 exe\x00\xe6\x9d\xe2\x9b\xb2\xd1\xd6CK\x8b)\xaewZ\xd8\xc2\xe4\x8cS\x91100644 normal\x00\xe6\x9d\xe2\x9b\xb2\xd1\xd6CK\x8b)\xaewZ\xd8\xc2\xe4\x8cS\x91120000 symlink\x00Z\xe04cN\x8d8,\x86F\x06\x8b\x8bR8\x18\x15\xed\xab\xf0";
        let entry = Entry(Bytes::copy_from_slice(data), TreeFormat::Git);
        let elements = entry.elements();
        let elements_str = elements
            .map(|e| format!("{:?}", e.unwrap()))
            .collect::<Vec<_>>();
        assert_eq!(
            elements_str,
            [
                "Element { component: PathComponentBuf(\"dir\"), hgid: HgId(\"8dc877a998d8c61f900e8b4ee9b501fa0a039358\"), flag: Directory }",
                "Element { component: PathComponentBuf(\"exe\"), hgid: HgId(\"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391\"), flag: File(Executable) }",
                "Element { component: PathComponentBuf(\"normal\"), hgid: HgId(\"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391\"), flag: File(Regular) }",
                "Element { component: PathComponentBuf(\"symlink\"), hgid: HgId(\"5ae034634e8d382c8646068b8b52381815edabf0\"), flag: File(Symlink) }",
            ]
        );
    }

    #[test]
    fn test_lookup_order() {
        let elements = vec![
            element("b-"),
            element("b0"),
            element("b/"),
            element("a"),
            element("c"),
        ];
        for format in [TreeFormat::Git, TreeFormat::Hg] {
            let entry = Entry::from_elements(elements.clone(), format);
            // Exercise the `assert_eq!` about `lookup` in `next()`.
            let _ = entry.elements().collect::<Vec<_>>();
        }
    }

    #[test]
    fn test_roundtrip_serialization_on_directory() {
        let component = PathComponentBuf::from_string(String::from("c")).unwrap();
        let hgid = HgId::from_hex(b"2e31d52f551e445002a6e6690700ce2ac31f196e").unwrap();
        let flag = Flag::Directory;
        let byte_slice = b"c\02e31d52f551e445002a6e6690700ce2ac31f196et";
        let element = Element::new(component, hgid, flag);
        assert_eq!(Element::from_byte_slice_hg(byte_slice).unwrap(), element);
        let buffer = element.to_byte_vec_hg();
        assert_eq!(buffer.to_vec(), byte_slice.to_vec());
    }

    quickcheck! {
        fn test_rountrip_serialization(
            component: PathComponentBuf,
            hgid: HgId,
            flag_proxy: Option<FileType>
        ) -> bool {
            let flag = match flag_proxy {
                Some(file_type) => Flag::File(file_type),
                None => Flag::Directory,
            };
            let element = Element::new(component, hgid, flag);
            let buffer = element.to_byte_vec_hg();
            Element::from_byte_slice_hg(&buffer).unwrap() == element
        }
    }

    fn element(name: &str) -> Element {
        let (name, flag) = crate::namecmp::tests::get_name_flag(name);
        Element::new(name, HgId::default(), flag)
    }
}