/*
 * 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::HashMap;
use std::collections::HashSet;

use anyhow::Result;
use context::CoreContext;
use mercurial_mutation::HgMutationEntry;
use mercurial_mutation::HgMutationStore;
use mercurial_types::HgChangesetId;

fn compare_entries(a: &HgMutationEntry, b: &HgMutationEntry) -> Ordering {
    a.successor().cmp(b.successor())
}

pub(crate) fn get_entries(
    entries: &HashMap<usize, HgMutationEntry>,
    indexes: &[usize],
) -> Vec<HgMutationEntry> {
    let mut result = Vec::new();
    for index in indexes.iter() {
        if let Some(entry) = entries.get(index) {
            result.push(entry.clone());
        }
    }
    result
}

fn get_successors(entries: &[HgMutationEntry]) -> Vec<&HgChangesetId> {
    entries.iter().map(|entry| entry.successor()).collect()
}

pub(crate) async fn check_entries(
    store: &dyn HgMutationStore,
    ctx: &CoreContext,
    changeset_ids: HashSet<HgChangesetId>,
    entries: &HashMap<usize, HgMutationEntry>,
    indexes: &[usize],
) -> Result<()> {
    let mut fetched_entries = store.all_predecessors(ctx, changeset_ids).await?;
    let mut expected_entries = get_entries(entries, indexes);
    fetched_entries.sort_unstable_by(compare_entries);
    expected_entries.sort_unstable_by(compare_entries);
    assert_eq!(
        get_successors(&fetched_entries),
        get_successors(&expected_entries)
    );
    assert_eq!(fetched_entries, expected_entries);
    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 thiserror::Error;

#[derive(Debug, Error)]
pub enum ErrorKind {
    #[error("Bundle2Decode: {0}")]
    Bundle2Decode(String),
    #[error("Revlog: {0}")]
    Revlog(String),
    #[error("Repo: {0}")]
    Repo(String),
    #[error("Path: {0}")]
    Path(String),
    #[error("Unknown requirement: {0}")]
    UnknownReq(String),
    #[error("invalid Thrift structure '{0}': {1}")]
    InvalidThrift(String, String),
}


/*
 * 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 errors;
pub mod manifest;
pub mod revlog;
pub mod revlogrepo;
pub mod stockbookmarks;

pub use crate::errors::*;
pub use crate::manifest::EntryContent;
pub use crate::manifest::RevlogEntry;
pub use crate::revlogrepo::RevlogManifest;
pub use crate::revlogrepo::RevlogRepo;
pub use crate::revlogrepo::RevlogRepoOptions;

pub mod changeset {
    pub use mercurial_types::blobs::serialize_cs;
    pub use mercurial_types::blobs::RevlogChangeset;
}
pub use crate::changeset::RevlogChangeset;

mod thrift {
    pub use mononoke_types_thrift::*;
}


/*
 * 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::collections::hash_map::Entry;
use std::collections::hash_map::HashMap;
use std::collections::HashSet;
use std::fmt;
use std::fmt::Display;
use std::fs;
use std::io::BufRead;
use std::io::BufReader;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use std::sync::RwLock;

use anyhow::format_err;
use anyhow::Context;
use anyhow::Error;
use anyhow::Result;
use futures::future;
use futures::stream;
use futures::Async;
use futures::IntoFuture;
use futures::Poll;
use futures::Stream;
use futures_ext::try_boxfuture;
use futures_ext::BoxFuture;
use futures_ext::BoxStream;
use futures_ext::FutureExt;
use futures_ext::StreamExt;
use mercurial_types::blobs::RevlogChangeset;
use mercurial_types::fncache_fsencode;
use mercurial_types::simple_fsencode;
use mercurial_types::HgChangesetId;
use mercurial_types::HgManifestId;
use mercurial_types::HgNodeHash;
use mercurial_types::MPath;
use mercurial_types::MPathElement;
use mercurial_types::RepoPath;

use crate::errors::ErrorKind;
pub use crate::manifest::RevlogManifest;
use crate::revlog::RevIdx;
use crate::revlog::Revlog;
use crate::revlog::RevlogIter;
use crate::stockbookmarks::StockBookmarks;

const DEFAULT_LOGS_CAPACITY: usize = 1000000;

#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Required {
    Store,
    Fncache,
    Dotencode,
    Generaldelta,
    Treemanifest,
    Manifestv2,
    Usefncache,
    Revlogv1,
    Largefiles,
    Lz4revlog,
    StoreRequirements,
    SqlDirstate,
    HgSql,
    TreeDirstate,
    TreeState,
    LFS,
}

impl Display for Required {
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        use self::Required::*;

        let s = match *self {
            Store => "store",
            Fncache => "fncache",
            Dotencode => "dotencode",
            Generaldelta => "generaldelta",
            Treemanifest => "treemanifest",
            Manifestv2 => "manifestv2",
            Usefncache => "usefncache",
            Revlogv1 => "revlogv1",
            Largefiles => "largefiles",
            Lz4revlog => "lz4revlog",
            StoreRequirements => "storerequirements",
            SqlDirstate => "sqldirstate",
            HgSql => "hgsql",
            TreeDirstate => "treedirstate",
            TreeState => "treestate",
            LFS => "lfs",
        };
        write!(fmt, "{}", s)
    }
}

impl FromStr for Required {
    type Err = Error;

    fn from_str(s: &str) -> Result<Required> {
        use self::Required::*;

        match s {
            "store" => Ok(Store),
            "fncache" => Ok(Fncache),
            "dotencode" => Ok(Dotencode),
            "generaldelta" => Ok(Generaldelta),
            "treemanifest" => Ok(Treemanifest),
            "manifestv2" => Ok(Manifestv2),
            "usefncache" => Ok(Usefncache),
            "revlogv1" => Ok(Revlogv1),
            "largefiles" => Ok(Largefiles),
            "lz4revlog" => Ok(Lz4revlog),
            "storerequirements" => Ok(StoreRequirements),
            "sqldirstate" => Ok(SqlDirstate),
            "hgsql" => Ok(HgSql),
            "treedirstate" => Ok(TreeDirstate),
            "treestate" => Ok(TreeState),
            "lfs" => Ok(LFS),
            unk => Err(ErrorKind::UnknownReq(unk.into()).into()),
        }
    }
}

#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum StoreRequired {}

impl Display for StoreRequired {
    fn fmt(&self, _fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        // This library currently dooesn't support any store requirements.
        unimplemented!()
    }
}

impl FromStr for StoreRequired {
    type Err = Error;

    fn from_str(s: &str) -> Result<StoreRequired> {
        let unk = s;
        Err(ErrorKind::UnknownReq(unk.into()).into())
    }
}

/// Representation of a whole Mercurial repo
///
/// `Repo` represents a whole repo: ie, the complete history of a set of files.
/// It consists of an overall history in the form of a DAG of revisions, or changesets.
/// This DAG will typically have a single initial version (though it could have more if
/// histories are merged) and one or more heads, which are revisions which have no children.
///
/// Some revisions can be explicitly named with "bookmarks", and they're often heads as well.
///
/// At the filesystem level, the repo consists of:
///  - the changelog: .hg/store/00changelog.\[di\]
///  - the manifest: .hg/store/00manifest.\[di\]
///  - the tree manifests: .hg/store/00manifesttree.\[di\] and .hg/store/meta/.../00manifest.i
///  - per-file histories: .hg/store/data/.../<file>.\[di\]
#[derive(Debug, Clone)]
pub struct RevlogRepo {
    basepath: PathBuf,                          // path to .hg directory
    requirements: HashSet<Required>,            // requirements
    store_requirements: HashSet<StoreRequired>, // store requirements
    changelog: Revlog,                          // changes
    inner: Arc<RwLock<RevlogInner>>,            // Inner parts
    inmemory_logs_capacity: usize, // Limit on the number of filelogs and tree revlogs in memory.
                                   // Note: there can be 2 * inmemory_logs_capacity revlogs in
                                   // memory in total: half for filelogs and half for revlogs.
}

pub struct RevlogRepoOptions {
    pub inmemory_logs_capacity: usize,
}

#[derive(Debug)]
struct RevlogInner {
    logcache: HashMap<RepoPath, Revlog>,
}

impl PartialEq<Self> for RevlogRepo {
    fn eq(&self, other: &Self) -> bool {
        self.basepath == other.basepath
            && self.requirements == other.requirements
            && Arc::ptr_eq(&self.inner, &other.inner)
    }
}
impl Eq for RevlogRepo {}

impl RevlogRepo {
    pub fn open<P: Into<PathBuf>>(base: P) -> Result<RevlogRepo> {
        let options = RevlogRepoOptions {
            inmemory_logs_capacity: DEFAULT_LOGS_CAPACITY,
        };
        RevlogRepo::open_with_options(base, options)
    }

    pub fn open_with_options<P: Into<PathBuf>>(
        base: P,
        options: RevlogRepoOptions,
    ) -> Result<RevlogRepo> {
        let base = base.into();
        let store = base.as_path().join("store");

        let changelog =
            Revlog::from_idx_with_data(store.join("00changelog.i"), None as Option<String>)?;

        let mut requirements = HashSet::new();
        let file = fs::File::open(base.join("requires")).context("Can't open `requires`")?;
        for line in BufReader::new(file).lines() {
            requirements.insert(line.context("Line read failed")?.parse()?);
        }

        let mut store_requirements = HashSet::new();
        if requirements.contains(&Required::StoreRequirements) {
            let store_requirements_file = store.join("requires");
            // A missing store/requires files is the same as an empty one.
            if store_requirements_file.exists() {
                let file = fs::File::open(store_requirements_file)
                    .context("Can't open `store/requires`")?;
                for line in BufReader::new(file).lines() {
                    store_requirements.insert(line.context("Line read failed")?.parse()?);
                }
            }
        }

        Ok(RevlogRepo {
            basepath: base,
            requirements,
            store_requirements,
            changelog,
            inner: Arc::new(RwLock::new(RevlogInner {
                logcache: HashMap::new(),
            })),
            inmemory_logs_capacity: options.inmemory_logs_capacity,
        })
    }

    pub fn get_heads(&self) -> BoxStream<HgNodeHash, Error> {
        match self.changelog.get_heads() {
            Err(e) => stream::once(Err(e)).boxify(),
            Ok(set) => stream::iter_ok(set.into_iter()).boxify(),
        }
    }

    pub fn get_bookmarks(&self) -> Result<StockBookmarks> {
        StockBookmarks::read(self.basepath.clone())
    }

    pub fn get_bookmark_value(
        &self,
        key: &dyn AsRef<[u8]>,
    ) -> BoxFuture<Option<HgChangesetId>, Error> {
        match self.get_bookmarks() {
            Ok(b) => b.get(key).boxify(),
            Err(e) => future::err(e).boxify(),
        }
    }

    pub fn changesets(&self) -> ChangesetStream {
        ChangesetStream::new(&self.changelog)
    }

    pub fn get_rev_idx_for_changeset(&self, changesetid: HgChangesetId) -> Result<RevIdx, Error> {
        let nodeid = changesetid.clone().into_nodehash();
        self.changelog.get_idx_by_nodeid(nodeid)
    }

    pub fn get_changeset(&self, changesetid: HgChangesetId) -> BoxFuture<RevlogChangeset, Error> {
        // TODO: (jsgf) T17932873 distinguish between not existing vs some other error
        let nodeid = changesetid.clone().into_nodehash();
        self.changelog
            .get_idx_by_nodeid(nodeid)
            .and_then(|idx| self.changelog.get_rev(idx))
            .and_then(RevlogChangeset::new)
            .into_future()
            .boxify()
    }

    pub fn get_root_manifest(&self, manifestid: HgManifestId) -> BoxFuture<RevlogManifest, Error> {
        // TODO: (jsgf) T17932873 distinguish between not existing vs some other error
        let nodeid = manifestid.clone().into_nodehash();
        let repo = self.clone();
        let revlog = try_boxfuture!(self.get_path_revlog(&RepoPath::root()));
        revlog
            .get_idx_by_nodeid(nodeid)
            .and_then(|idx| revlog.get_rev(idx))
            .and_then(move |rev| RevlogManifest::new(repo, rev))
            .into_future()
            .boxify()
    }

    pub fn get_requirements(&self) -> &HashSet<Required> {
        &self.requirements
    }

    pub fn get_store_requirements(&self) -> &HashSet<StoreRequired> {
        &self.store_requirements
    }

    /// This method is used by RevlogManifest to traverse the Revlogs in search of manifests and
    /// files. Users of this crate should rely on RevlogManifest traversal or use
    /// RevlogRepo::get_manifest directly.
    pub(crate) fn get_path_revlog(&self, path: &RepoPath) -> Result<Revlog> {
        use mercurial_types::RepoPath::*;

        if let Some(revlog) = self.get_path_revlog_from_cache(path) {
            return Ok(revlog);
        }
        let mut inner = self.inner.write().expect("poisoned lock");

        // We may have memory issues if we are keeping too many revlogs in memory.
        // Let's clear them when we have too much
        if inner.logcache.len() > self.inmemory_logs_capacity {
            inner.logcache.clear();
        }

        match inner.logcache.entry(path.clone()) {
            Entry::Occupied(log) => Ok(log.get().clone()),

            Entry::Vacant(missing) => {
                let revlog_path = match *path {
                    // .hg/store/00manifesttree
                    RootPath => MPath::new("00manifesttree")?,
                    // .hg/store/meta/<path>/00manifest
                    DirectoryPath(_) => MPath::new("meta")?
                        .join(MPath::iter_opt(path.mpath()))
                        .join(&MPath::new("00manifest")?),
                    // .hg/store/data/<path>
                    FilePath(_) => MPath::new("data")?.join(MPath::iter_opt(path.mpath())),
                };
                Ok(missing
                    .insert(self.init_revlog_from_path(revlog_path)?)
                    .clone())
            }
        }
    }

    fn get_path_revlog_from_cache(&self, path: &RepoPath) -> Option<Revlog> {
        let inner = self.inner.read().expect("poisoned lock");
        inner.logcache.get(path).cloned()
    }

    /// path is the path to the revlog files, but without the .i or .d extensions
    fn init_revlog_from_path(&self, path: MPath) -> Result<Revlog> {
        let mut elements: Vec<MPathElement> = path.into_iter().collect();
        let basename = elements.pop().ok_or_else(|| {
            format_err!("empty path provided to RevlogRepo::init_revlog_from_path")
        })?;

        let index_path = {
            let mut basename = Vec::from(basename.as_ref());
            basename.extend(b".i");
            elements.push(MPathElement::new(basename)?);
            self.fsencode_path(&elements)
        };
        elements.pop();

        let data_path = {
            let mut basename = Vec::from(basename.as_ref());
            basename.extend(b".d");
            elements.push(MPathElement::new(basename)?);
            self.fsencode_path(&elements)
        };

        let store_path = self.basepath.join("store");
        Revlog::from_idx_with_data(
            store_path.join(index_path),
            Some(store_path.join(data_path)),
        )
    }

    fn fsencode_path(&self, elements: &[MPathElement]) -> PathBuf {
        // Mercurial has a complicated logic of path encoding.
        // Code below matches core Mercurial logic from the commit
        // 75013952d8d9608f73cd45f68405fbd6ec112bf2 from file mercurial/store.py from the function
        // store(). The only caveat is that basicstore is not yet implemented
        let elements = elements.iter().map(|e| e.as_ref()).collect::<Vec<_>>();
        if self.requirements.contains(&Required::Store) {
            if self.requirements.contains(&Required::Fncache) {
                let dotencode = self.requirements.contains(&Required::Dotencode);
                fncache_fsencode(&elements, dotencode)
            } else {
                simple_fsencode(&elements)
            }
        } else {
            unimplemented!();
        }
    }
}

pub struct ChangesetStream(RevlogIter);

impl ChangesetStream {
    fn new(changelog: &Revlog) -> Self {
        ChangesetStream(changelog.into_iter())
    }
}

impl Stream for ChangesetStream {
    type Item = (RevIdx, HgNodeHash);
    type Error = Error;

    fn poll(&mut self) -> Poll<Option<Self::Item>, Error> {
        match self.0.next() {
            Some((revidx, e)) => Ok(Async::Ready(Some((revidx, e.nodeid)))),
            None => Ok(Async::Ready(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::collections::HashMap;
use std::fs;
use std::io;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Read;
use std::path::PathBuf;

use anyhow::bail;
use anyhow::Context;
use anyhow::Error;
use anyhow::Result;
use ascii::AsciiStr;
use futures::future;
use futures::stream;
use futures::stream::Stream;
use futures_ext::BoxFuture;
use futures_ext::BoxStream;
use futures_ext::StreamExt;
use mercurial_types::HgChangesetId;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum ErrorKind {
    #[error("invalid bookmarks line: {0}")]
    InvalidBookmarkLine(String),
    #[error("invalid hash: {0}")]
    InvalidHash(String),
}

/// Implementation of bookmarks as they exist in stock Mercurial inside `.hg/bookmarks`.
/// The file has a list of entries:
///
/// ```text
/// <hash1> <bookmark1-name>
/// <hash2> <bookmark2-name>
/// ...
/// ```
///
/// Bookmark names are arbitrary bytestrings, and hashes are always HgChangesetIds.
///
/// This implementation is read-only -- implementing write support would require interacting with
/// the locking mechanism Mercurial uses, and generally seems like it wouldn't be very useful.
#[derive(Clone, Debug)]
pub struct StockBookmarks {
    bookmarks: HashMap<Vec<u8>, HgChangesetId>,
}

impl StockBookmarks {
    pub fn read<P: Into<PathBuf>>(base: P) -> Result<Self> {
        let base = base.into();

        // Newer clients store bookmarks in storevfs.
        let mut path = base.join("store/bookmarks");
        if !path.exists() {
            // Older clients store bookmarks in vfs.
            path = base.join("bookmarks");
        }
        let file = fs::File::open(path);
        match file {
            Ok(file) => Self::from_reader(file),
            Err(ref err) if err.kind() == io::ErrorKind::NotFound => {
                // The .hg/bookmarks file is not guaranteed to exist. Treat it is empty if it
                // doesn't.
                Ok(StockBookmarks {
                    bookmarks: HashMap::new(),
                })
            }
            Err(err) => Err(err.into()),
        }
    }

    fn from_reader<R: Read>(reader: R) -> Result<Self> {
        let mut bookmarks = HashMap::new();

        // Bookmark names might not be valid UTF-8, so use split() instead of lines().
        for line in BufReader::new(reader).split(b'\n') {
            let line = line?;
            // <hash><space><bookmark name>, where hash is 40 bytes, the space is 1 byte
            // and the bookmark name is at least 1 byte.
            if line.len() < 42 || line[40] != b' ' {
                bail!(ErrorKind::InvalidBookmarkLine(
                    String::from_utf8_lossy(line.as_ref()).into_owned(),
                ));
            }
            let bmname = &line[41..];
            let hash_slice = &line[..40];
            let hash = AsciiStr::from_ascii(&hash_slice).with_context(|| {
                ErrorKind::InvalidHash(String::from_utf8_lossy(hash_slice).into_owned())
            })?;
            bookmarks.insert(
                bmname.into(),
                HgChangesetId::from_ascii_str(hash).with_context(|| {
                    ErrorKind::InvalidHash(String::from_utf8_lossy(hash_slice).into_owned())
                })?,
            );
        }

        Ok(StockBookmarks { bookmarks })
    }

    pub fn get(&self, name: &dyn AsRef<[u8]>) -> BoxFuture<Option<HgChangesetId>, Error> {
        let value = self.bookmarks.get(name.as_ref()).copied();
        Box::new(future::result(Ok(value)))
    }

    pub fn keys(&self) -> BoxStream<Vec<u8>, Error> {
        // collect forces evaluation early, so that the stream can safely outlive self
        stream::iter_ok(
            self.bookmarks
                .keys()
                .map(|k| Ok(k.to_vec()))
                .collect::<Vec<_>>(),
        )
        .and_then(|x| x)
        .boxify()
    }
}

#[cfg(test)]
mod tests {
    use std::io::Cursor;

    use assert_matches::assert_matches;
    use failure_ext::err_downcast;
    use failure_ext::err_downcast_ref;
    use futures::Future;
    use mercurial_types_mocks::nodehash::*;

    use super::*;

    fn assert_bookmark_get(
        bookmarks: &StockBookmarks,
        key: &dyn AsRef<[u8]>,
        expected: Option<HgChangesetId>,
    ) {
        assert_eq!(bookmarks.get(key).wait().unwrap(), expected);
    }

    #[test]
    fn test_parse() {
        let disk_bookmarks = b"\
            1111111111111111111111111111111111111111 abc\n\
            2222222222222222222222222222222222222222 def\n\
            1111111111111111111111111111111111111111 test123\n";
        let reader = Cursor::new(&disk_bookmarks[..]);

        let bookmarks = StockBookmarks::from_reader(reader).unwrap();
        assert_bookmark_get(&bookmarks, &"abc", Some(HgChangesetId::new(ONES_HASH)));
        assert_bookmark_get(&bookmarks, &"def", Some(HgChangesetId::new(TWOS_HASH)));
        assert_bookmark_get(&bookmarks, &"test123", Some(HgChangesetId::new(ONES_HASH)));

        // Bookmarks that aren't present
        assert_bookmark_get(&bookmarks, &"abcdef", None);

        // keys should return all the keys here
        let mut list = bookmarks.keys().collect().wait().unwrap();
        list.sort();
        assert_eq!(list, vec![&b"abc"[..], &b"def"[..], &b"test123"[..]]);
    }

    /// Test a bunch of invalid bookmark lines
    #[test]
    fn test_invalid() {
        let reader = Cursor::new(&b"111\n"[..]);
        let bookmarks = StockBookmarks::from_reader(reader);
        assert_matches!(
            err_downcast!(bookmarks.unwrap_err(), e: ErrorKind => e).unwrap(),
            ErrorKind::InvalidBookmarkLine(_)
        );

        // no space or bookmark name
        let reader = Cursor::new(&b"1111111111111111111111111111111111111111\n"[..]);
        let bookmarks = StockBookmarks::from_reader(reader);
        assert_matches!(
            err_downcast!(bookmarks.unwrap_err(), e: ErrorKind => e).unwrap(),
            ErrorKind::InvalidBookmarkLine(_)
        );

        // no bookmark name
        let reader = Cursor::new(&b"1111111111111111111111111111111111111111 \n"[..]);
        let bookmarks = StockBookmarks::from_reader(reader);
        assert_matches!(
            err_downcast!(bookmarks.unwrap_err(), e: ErrorKind => e).unwrap(),
            ErrorKind::InvalidBookmarkLine(_)
        );

        // no space after hash
        let reader = Cursor::new(&b"1111111111111111111111111111111111111111ab\n"[..]);
        let bookmarks = StockBookmarks::from_reader(reader);
        assert_matches!(
            err_downcast!(bookmarks.unwrap_err(), e: ErrorKind => e).unwrap(),
            ErrorKind::InvalidBookmarkLine(_)
        );

        // short hash
        let reader = Cursor::new(&b"111111111111111111111111111111111111111  1ab\n"[..]);
        let bookmarks = StockBookmarks::from_reader(reader);
        let err = bookmarks.unwrap_err();
        match err_downcast_ref!(err, err: ErrorKind => err) {
            Some(ok @ ErrorKind::InvalidHash(..)) => println!("OK: {:?}", ok),
            Some(bad) => panic!("other ErrorKind error: {:?}", bad),
            None => panic!("other error: {:?}", err),
        };

        // non-ASCII
        let reader = Cursor::new(&b"111111111111111111111111111111111111111\xff test\n"[..]);
        let err = StockBookmarks::from_reader(reader).unwrap_err();
        match err_downcast_ref!(err, err: ErrorKind => err) {
            Some(ok @ ErrorKind::InvalidHash(..)) => println!("OK: {:?}", ok),
            Some(bad) => panic!("other ErrorKind error: {:?}", bad),
            None => panic!("other error: {:?}", err),
        };

        // not a valid hex string
        let reader = Cursor::new(&b"abcdefgabcdefgabcdefgabcdefgabcdefgabcde test\n"[..]);
        let err = StockBookmarks::from_reader(reader).unwrap_err();
        match err_downcast_ref!(err, err: ErrorKind => err) {
            Some(ok @ ErrorKind::InvalidHash(..)) => println!("OK: {:?}", ok),
            Some(bad) => panic!("other ErrorKind error: {:?}", bad),
            None => panic!("other error: {:?}", err),
        };
    }
}


/*
 * 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::collections::HashSet;
use std::collections::VecDeque;
use std::hash::Hash;
use std::hash::Hasher;

use anyhow::Error;
use futures::future::Future;
use futures::stream::empty;
use futures::stream::iter_ok;
use futures::stream::once;
use futures::stream::Stream;
use futures_ext::BoxStream;
use futures_ext::StreamExt;
use mercurial_types::MPath;
use mercurial_types::MPathElement;
use mercurial_types::Type;

use super::revlog::EntryContent;
use super::RevlogEntry;
use super::RevlogManifest;

// Note that:
// * this isn't "left" and "right" because an explicit direction makes the API clearer
// * this isn't "new" and "old" because one could ask for either a diff from a child manifest to
//   its parent, or the other way round
pub enum EntryStatus {
    Added(RevlogEntry),
    Deleted(RevlogEntry),
    // Entries will always either be File or Tree. However, it's possible for one of the entries
    // to be Regular and the other to be Symlink, etc.
    Modified {
        to_entry: RevlogEntry,
        from_entry: RevlogEntry,
    },
}

pub struct ChangedEntry {
    pub path: Option<MPath>,
    pub status: EntryStatus,
}

impl ChangedEntry {
    pub fn new_added(path: Option<MPath>, entry: RevlogEntry) -> Self {
        ChangedEntry {
            path,
            status: EntryStatus::Added(entry),
        }
    }

    pub fn new_deleted(path: Option<MPath>, entry: RevlogEntry) -> Self {
        ChangedEntry {
            path,
            status: EntryStatus::Deleted(entry),
        }
    }

    pub fn new_modified(
        path: Option<MPath>,
        to_entry: RevlogEntry,
        from_entry: RevlogEntry,
    ) -> Self {
        ChangedEntry {
            path,
            status: EntryStatus::Modified {
                to_entry,
                from_entry,
            },
        }
    }
}

struct NewEntry {
    path: Option<MPath>,
    entry: RevlogEntry,
}

impl NewEntry {
    fn from_changed_entry(ce: ChangedEntry) -> Option<Self> {
        let path = ce.path;
        match ce.status {
            EntryStatus::Deleted(_) => None,
            EntryStatus::Added(entry)
            | EntryStatus::Modified {
                to_entry: entry, ..
            } => Some(Self { path, entry }),
        }
    }

    fn into_tuple(self) -> (Option<MPath>, RevlogEntry) {
        (self.path, self.entry)
    }
}

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

impl Hash for NewEntry {
    fn hash<H>(&self, state: &mut H)
    where
        H: Hasher,
    {
        self.path.hash(state);
    }
}

/// For a given Manifests and list of parents this function recursively compares their content and
/// returns a intersection of entries that the given Manifest had added (both newly added and
/// replacement for modified entries) compared to it's parents
///
/// TODO(luk, T26981580) This implementation is not efficient, because in order to find the
///                      intersection of parents it first produces the full difference of root vs
///                      each parent, then puts /// one parent in a HashSet and performs the
///                      intersection.
///                      A better approach would be to traverse the manifest tree of root and both
///                      parents simultaniously and produce the intersection result while
///                      traversing
pub fn new_entry_intersection_stream(
    root: &RevlogManifest,
    p1: Option<&RevlogManifest>,
    p2: Option<&RevlogManifest>,
) -> BoxStream<(Option<MPath>, RevlogEntry), Error> {
    if let (Some(p1), Some(p2)) = (p1, p2) {
        let p1 = changed_entry_stream(root, p1, None).filter_map(NewEntry::from_changed_entry);
        let p2 = changed_entry_stream(root, p2, None).filter_map(NewEntry::from_changed_entry);

        p2.collect()
            .map(move |p2| {
                let p2: HashSet<_> = HashSet::from_iter(p2.into_iter());

                p1.filter_map(move |ne| if p2.contains(&ne) { Some(ne) } else { None })
            })
            .flatten_stream()
            .map(NewEntry::into_tuple)
            .boxify()
    } else {
        let ces = if let Some(p1) = p1 {
            changed_entry_stream(root, p1, None)
        } else if let Some(p2) = p2 {
            changed_entry_stream(root, p2, None)
        } else {
            changed_entry_stream(root, &RevlogManifest::empty(), None)
        };

        ces.filter_map(NewEntry::from_changed_entry)
            .map(NewEntry::into_tuple)
            .boxify()
    }
}

/// Given two manifests, returns a difference between them. Difference is a stream of
/// ChangedEntry, each showing whether a file/directory was added, deleted or modified.
/// Note: Modified entry contains only entries of the same type i.e. if a file was replaced
/// with a directory of the same name, then returned stream will contain Deleted file entry,
/// and Added directory entry. The same *does not* apply for changes between the various
/// file types (Regular, Executable and Symlink): those will only be one Modified entry.
pub fn changed_entry_stream(
    to: &RevlogManifest,
    from: &RevlogManifest,
    path: Option<MPath>,
) -> BoxStream<ChangedEntry, Error> {
    diff_manifests(path, to, from)
        .map(recursive_changed_entry_stream)
        .flatten()
        .boxify()
}

/// Given a ChangedEntry, return a stream that consists of this entry, and all subentries
/// that differ. If input isn't a tree, then a stream with a single entry is returned, otherwise
/// subtrees are recursively compared.
fn recursive_changed_entry_stream(changed_entry: ChangedEntry) -> BoxStream<ChangedEntry, Error> {
    match changed_entry.status {
        EntryStatus::Added(entry) => recursive_entry_stream(changed_entry.path, entry)
            .map(|(path, entry)| ChangedEntry::new_added(path, entry))
            .boxify(),
        EntryStatus::Deleted(entry) => recursive_entry_stream(changed_entry.path, entry)
            .map(|(path, entry)| ChangedEntry::new_deleted(path, entry))
            .boxify(),
        EntryStatus::Modified {
            to_entry,
            from_entry,
        } => {
            debug_assert!(to_entry.get_type().is_tree() == from_entry.get_type().is_tree());

            let substream = if to_entry.get_type().is_tree() {
                let contents = to_entry.get_content().join(from_entry.get_content());
                let path = changed_entry.path.clone();
                let entry_path = to_entry.get_name().cloned();

                let substream = contents
                    .map(move |(to_content, from_content)| {
                        let to_manifest = get_tree_content(to_content);
                        let from_manifest = get_tree_content(from_content);

                        diff_manifests(
                            MPath::join_element_opt(path.as_ref(), entry_path.as_ref()),
                            &to_manifest,
                            &from_manifest,
                        )
                        .map(recursive_changed_entry_stream)
                    })
                    .flatten_stream()
                    .flatten();

                substream.boxify()
            } else {
                empty().boxify()
            };

            let current_entry = once(Ok(ChangedEntry::new_modified(
                changed_entry.path.clone(),
                to_entry,
                from_entry,
            )));
            current_entry.chain(substream).boxify()
        }
    }
}

/// Given an entry and path from the root of the repo to this entry, returns all subentries with
/// their path from the root of the repo.
/// For a non-tree entry returns a stream with a single (entry, path) pair.
pub fn recursive_entry_stream(
    rootpath: Option<MPath>,
    entry: RevlogEntry,
) -> BoxStream<(Option<MPath>, RevlogEntry), Error> {
    let subentries = match entry.get_type() {
        Type::File(_) => empty().boxify(),
        Type::Tree => {
            let entry_basename = entry.get_name();
            let path = MPath::join_opt(rootpath.as_ref(), entry_basename);

            entry
                .get_content()
                .map(|content| {
                    get_tree_content(content)
                        .list()
                        .map(move |entry| recursive_entry_stream(path.clone(), entry))
                })
                .flatten_stream()
                .flatten()
                .boxify()
        }
    };

    once(Ok((rootpath, entry))).chain(subentries).boxify()
}

/// Difference between manifests, non-recursive.
/// It fetches manifest content, sorts it and compares.
fn diff_manifests(
    path: Option<MPath>,
    to: &RevlogManifest,
    from: &RevlogManifest,
) -> BoxStream<ChangedEntry, Error> {
    let to_vec_future = to.list().collect();
    let from_vec_future = from.list().collect();

    to_vec_future
        .join(from_vec_future)
        .map(|(to, from)| iter_ok(diff_sorted_vecs(path, to, from).into_iter()))
        .flatten_stream()
        .boxify()
}

/// Compares vectors of entries and returns the difference
// TODO(stash): T25644857 this method is made public to make it possible to test it.
// Otherwise we need create dependency to mercurial_types_mocks, which depends on mercurial_types.
// This causing compilation failure.
// We need to find a workaround for an issue.
pub fn diff_sorted_vecs(
    path: Option<MPath>,
    to: Vec<RevlogEntry>,
    from: Vec<RevlogEntry>,
) -> Vec<ChangedEntry> {
    let mut to = VecDeque::from(to);
    let mut from = VecDeque::from(from);

    let mut res = vec![];
    loop {
        match (to.pop_front(), from.pop_front()) {
            (Some(to_entry), Some(from_entry)) => {
                let to_path: Option<MPathElement> = to_entry.get_name().cloned();
                let from_path: Option<MPathElement> = from_entry.get_name().cloned();

                // note that for Option types, None is less than any Some
                if to_path < from_path {
                    res.push(ChangedEntry::new_added(path.clone(), to_entry));
                    from.push_front(from_entry);
                } else if to_path > from_path {
                    res.push(ChangedEntry::new_deleted(path.clone(), from_entry));
                    to.push_front(to_entry);
                } else if to_entry.get_type().is_tree() == from_entry.get_type().is_tree() {
                    if to_entry.get_hash() != from_entry.get_hash() {
                        res.push(ChangedEntry::new_modified(
                            path.clone(),
                            to_entry,
                            from_entry,
                        ));
                    }
                } else {
                    res.push(ChangedEntry::new_added(path.clone(), to_entry));
                    res.push(ChangedEntry::new_deleted(path.clone(), from_entry));
                }
            }

            (Some(to_entry), None) => {
                res.push(ChangedEntry::new_added(path.clone(), to_entry));
            }

            (None, Some(from_entry)) => {
                res.push(ChangedEntry::new_deleted(path.clone(), from_entry));
            }
            (None, None) => {
                break;
            }
        }
    }

    res
}

fn get_tree_content(content: EntryContent) -> RevlogManifest {
    match content {
        EntryContent::Tree(manifest) => manifest,
        _ => panic!("Tree entry was expected"),
    }
}


/*
 * 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 manifest_utils;
mod revlog;

pub use self::manifest_utils::new_entry_intersection_stream;
pub use self::revlog::Details;
pub use self::revlog::EntryContent;
pub use self::revlog::ManifestContent;
pub use self::revlog::RevlogEntry;
pub use self::revlog::RevlogManifest;


/*
 * 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::collections::BTreeMap;
use std::io;
use std::io::Write;
use std::str;
use std::vec;

use anyhow::bail;
use anyhow::ensure;
use anyhow::format_err;
use anyhow::Context;
use anyhow::Error;
use anyhow::Result;
use futures::future;
use futures::future::Future;
use futures::future::IntoFuture;
use futures::stream;
use futures::stream::Stream;
use futures::Async;
use futures::Poll;
use futures_ext::BoxFuture;
use futures_ext::BoxStream;
use futures_ext::FutureExt;
use futures_ext::StreamExt;
use mercurial_types::blobs::file;
use mercurial_types::manifest::Type;
use mercurial_types::FileType;
use mercurial_types::HgBlob;
use mercurial_types::HgBlobNode;
use mercurial_types::HgEntryId;
use mercurial_types::HgFileNodeId;
use mercurial_types::HgManifestId;
use mercurial_types::HgNodeHash;
use mercurial_types::HgParents;
use mercurial_types::MPath;
use mercurial_types::MPathElement;
use mercurial_types::RepoPath;

use crate::errors::ErrorKind;
use crate::RevlogRepo;

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct Details {
    hash: HgNodeHash,
    flag: Type,
}

/// Revlog Manifest v1
#[derive(Debug, PartialEq)]
pub struct RevlogManifest {
    // This is None so that a RevlogManifest::empty() can be created for easy diffing manifests
    repo: Option<RevlogRepo>,
    parents: HgParents,
    content: ManifestContent,
}

/// Concrete representation of various Entry Types.
pub enum EntryContent {
    File(file::File),       // TODO stream
    Executable(file::File), // TODO stream
    // Symlinks typically point to files but can have arbitrary content, so represent them as
    // blobs rather than as MPath instances.
    Symlink(file::File),
    Tree(RevlogManifest),
}

#[derive(Debug, Eq, PartialEq)]
pub struct ManifestContent {
    pub files: BTreeMap<MPath, Details>,
}

impl ManifestContent {
    // Each manifest revision contains a list of the file revisions in each changeset, in the form:
    //
    // <filename>\0<hex file revision id>[<flags>]\n
    //
    // Source: mercurial/parsers.c:parse_manifest()
    //
    // NB: filenames are sequences of non-zero bytes, not strings
    fn parse_impl(data: &[u8], prefix: Option<&MPath>) -> Result<BTreeMap<MPath, Details>> {
        let mut files = BTreeMap::new();

        for line in data.split(|b| *b == b'\n') {
            if line.is_empty() {
                break;
            }

            let (name, rest) = match find(line, &0) {
                None => bail!("Malformed entry: no \\0"),
                Some(nil) => {
                    let (name, rest) = line.split_at(nil);
                    if let Some((_, hash)) = rest.split_first() {
                        (name, hash)
                    } else {
                        bail!("Malformed entry: no hash");
                    }
                }
            };

            let path = if let Some(prefix) = prefix {
                prefix.join(&MPath::new(name).context("invalid path in manifest")?)
            } else {
                MPath::new(name).context("invalid path in manifest")?
            };
            let details = Details::parse(rest)?;

            // XXX check path > last entry in files
            files.insert(path, details);
        }

        Ok(files)
    }

    pub fn parse(data: &[u8]) -> Result<Self> {
        Ok(Self {
            files: Self::parse_impl(data, None)?,
        })
    }

    pub fn parse_with_prefix(data: &[u8], prefix: &MPath) -> Result<Self> {
        Ok(Self {
            files: Self::parse_impl(data, Some(prefix))?,
        })
    }

    pub fn generate<W: Write>(&self, out: &mut W) -> io::Result<()> {
        for (k, v) in &self.files {
            k.generate(out)?;
            out.write_all(&b"\0"[..])?;
            v.generate(out)?;
            out.write_all(&b"\n"[..])?;
        }
        Ok(())
    }
}

impl RevlogManifest {
    pub fn new(repo: RevlogRepo, node: HgBlobNode) -> Result<RevlogManifest> {
        Self::parse(Some(repo), node.parents(), node.as_blob().as_slice())
    }

    pub(crate) fn empty() -> Self {
        Self {
            repo: None,
            parents: HgParents::new(None, None),
            content: ManifestContent {
                files: BTreeMap::new(),
            },
        }
    }

    fn parse(repo: Option<RevlogRepo>, parents: &HgParents, data: &[u8]) -> Result<RevlogManifest> {
        // This is private because it allows one to create a RevlogManifest with repo set to None.
        ManifestContent::parse(data).map(|content| RevlogManifest {
            repo,
            parents: parents.clone(),
            content,
        })
    }

    fn parse_with_prefix(
        repo: RevlogRepo,
        parents: &HgParents,
        data: &[u8],
        prefix: &MPath,
    ) -> Result<RevlogManifest> {
        ManifestContent::parse_with_prefix(data, prefix).map(|content| RevlogManifest {
            repo: Some(repo),
            parents: parents.clone(),
            content,
        })
    }

    pub fn parents(&self) -> &HgParents {
        &self.parents
    }

    pub fn generate<W: Write>(&self, out: &mut W) -> io::Result<()> {
        self.content.generate(out)
    }

    pub fn manifest(&self) -> Vec<(&MPath, &Details)> {
        self.content.files.iter().collect()
    }

    pub fn lookup(&self, path: &MPath) -> BoxFuture<Option<RevlogEntry>, Error> {
        let repo = match self.repo {
            Some(ref repo) => repo.clone(),
            None => return future::ok(None).boxify(),
        };
        let res = self
            .content
            .files
            .get(path)
            .map(|details| RevlogEntry::new(repo, path.clone(), *details));

        match res {
            Some(v) => v.map(Some).into_future().boxify(),
            None => Ok(None).into_future().boxify(),
        }
    }

    pub fn list(&self) -> BoxStream<RevlogEntry, Error> {
        let repo = match self.repo {
            Some(ref repo) => repo.clone(),
            None => return stream::empty().boxify(),
        };
        let v: Vec<_> = self
            .manifest()
            .into_iter()
            .map(|(p, d)| (p.clone(), *d))
            .collect();
        RevlogListStream(v.into_iter(), repo).boxify()
    }
}

impl Details {
    pub fn new(hash: HgNodeHash, flag: Type) -> Self {
        Self { hash, flag }
    }

    fn parse(data: &[u8]) -> Result<Details> {
        ensure!(data.len() >= 40, "hash too small: {:?}", data);

        let (hash, flags) = data.split_at(40);
        let hash = str::from_utf8(hash)
            .map_err(Error::from)
            .and_then(|hash| hash.parse::<HgNodeHash>())
            .with_context(|| format!("malformed hash: {:?}", hash))?;

        ensure!(flags.len() <= 1, "More than 1 flag: {:?}", flags);

        let flag = if flags.is_empty() {
            Type::File(FileType::Regular)
        } else {
            match flags[0] {
                b'l' => Type::File(FileType::Symlink),
                b'x' => Type::File(FileType::Executable),
                b't' => Type::Tree,
                unk => bail!("Unknown flag {}", unk),
            }
        };

        Ok(Details { hash, flag })
    }

    fn generate<W: Write>(&self, out: &mut W) -> io::Result<()> {
        write!(out, "{}{}", self.hash, self.flag.manifest_suffix())
    }

    pub fn entryid(&self) -> HgEntryId {
        match self.flag {
            Type::File(file_type) => HgEntryId::File(file_type, HgFileNodeId::new(self.hash)),
            Type::Tree => HgEntryId::Manifest(HgManifestId::new(self.hash)),
        }
    }

    pub fn flag(&self) -> Type {
        self.flag
    }

    pub fn is_symlink(&self) -> bool {
        self.flag == Type::File(FileType::Symlink)
    }

    pub fn is_tree(&self) -> bool {
        self.flag == Type::Tree
    }

    pub fn is_executable(&self) -> bool {
        self.flag == Type::File(FileType::Executable)
    }

    pub fn is_file(&self) -> bool {
        self.flag == Type::File(FileType::Regular)
    }
}

fn find<T>(haystack: &[T], needle: &T) -> Option<usize>
where
    T: PartialEq,
{
    haystack.iter().position(|e| e == needle)
}

pub struct RevlogEntry {
    repo: RevlogRepo,
    path: RepoPath,
    name: Option<MPathElement>,
    details: Details,
}

pub struct RevlogListStream(vec::IntoIter<(MPath, Details)>, RevlogRepo);

impl Stream for RevlogListStream {
    type Item = RevlogEntry;
    type Error = Error;

    fn poll(&mut self) -> Poll<Option<Self::Item>, Error> {
        let v = self
            .0
            .next()
            .map(|(path, details)| RevlogEntry::new(self.1.clone(), path, details));
        match v {
            Some(v) => v.map(|x| Async::Ready(Some(x))),
            None => Ok(Async::Ready(None)),
        }
    }
}

impl RevlogEntry {
    fn new(repo: RevlogRepo, path: MPath, details: Details) -> Result<Self> {
        let name = (&path).into_iter().next_back().cloned();
        let path = match details.flag() {
            Type::Tree => RepoPath::dir(path)
                .with_context(|| ErrorKind::Path("error while creating RepoPath".into()))?,
            _ => RepoPath::file(path)
                .with_context(|| ErrorKind::Path("error while creating RepoPath".into()))?,
        };

        // For revlog we still need to store full path, because full path is used to find revlog
        // file
        Ok(RevlogEntry {
            repo,
            path,
            name,
            details,
        })
    }

    fn get_path(&self) -> &RepoPath {
        &self.path
    }

    pub fn get_type(&self) -> Type {
        self.details.flag()
    }

    pub fn get_parents(&self) -> BoxFuture<HgParents, Error> {
        let revlog = self.repo.get_path_revlog(self.get_path());
        let nodeid = self.get_hash().into_nodehash();
        revlog
            .and_then(|revlog| revlog.get_rev_parents_by_nodeid(nodeid))
            .into_future()
            .boxify()
    }

    pub fn get_raw_content(&self) -> BoxFuture<HgBlob, Error> {
        let revlog = self.repo.get_path_revlog(self.get_path());
        let nodeid = self.get_hash().into_nodehash();
        revlog
            .and_then(|revlog| revlog.get_rev_by_nodeid(nodeid))
            .map(|node| node.as_blob().clone())
            .map_err(|err| {
                err.context(format_err!(
                    "Can't get content for {} node {}",
                    self.get_path(),
                    nodeid
                ))
            })
            .map_err(Error::from)
            .into_future()
            .boxify()
    }

    pub fn is_ext(&self) -> BoxFuture<bool, Error> {
        let revlog = self.repo.get_path_revlog(self.get_path());
        let nodeid = self.get_hash().into_nodehash();

        revlog
            .and_then(|revlog| revlog.is_ext_by_nodeid(nodeid))
            .map_err(Error::from)
            .into_future()
            .boxify()
    }

    pub fn get_content(&self) -> BoxFuture<EntryContent, Error> {
        let revlog = self.repo.get_path_revlog(self.get_path());
        let nodeid = self.get_hash().into_nodehash();

        revlog
            .and_then(|revlog| revlog.get_rev_by_nodeid(nodeid))
            .and_then(|node| {
                let data = node.as_blob();
                match self.get_type() {
                    Type::File(ft) => {
                        let f = file::File::data_only(data.clone());
                        let content = match ft {
                            FileType::Regular => EntryContent::File(f),
                            FileType::Executable => EntryContent::File(f),
                            FileType::Symlink => EntryContent::File(f),
                        };
                        Ok(content)
                    }
                    Type::Tree => {
                        let data = data.as_slice();
                        let revlog_manifest = RevlogManifest::parse_with_prefix(
                            self.repo.clone(),
                            node.parents(),
                            data,
                            self.get_path()
                                .mpath()
                                .expect("trees should always have a path"),
                        )?;
                        Ok(EntryContent::Tree(revlog_manifest))
                    }
                }
            })
            .map_err(|err| {
                err.context(format_err!(
                    "Can't get content for {} node {}",
                    self.get_path(),
                    nodeid
                ))
            })
            .map_err(Error::from)
            .into_future()
            .boxify()
    }

    pub fn get_size(&self) -> BoxFuture<Option<usize>, Error> {
        self.get_content()
            .and_then(|content| match content {
                EntryContent::File(data)
                | EntryContent::Executable(data)
                | EntryContent::Symlink(data) => Ok(Some(data.content().len())),
                EntryContent::Tree(_) => Ok(None),
            })
            .boxify()
    }

    pub fn get_hash(&self) -> HgEntryId {
        self.details.entryid()
    }

    pub fn get_name(&self) -> Option<&MPathElement> {
        self.name.as_ref()
    }
}

#[cfg(test)]
mod test {
    use mercurial_types_mocks::nodehash::*;

    use super::*;

    #[test]
    fn test_find() {
        assert_eq!(find(b"abc123", &b'b'), Some(1));
        assert_eq!(find(b"abc123", &b'x'), None);
        assert_eq!(find(b"abc123abc", &b'b'), Some(1));
        assert_eq!(find(b"", &b'b'), None);
    }

    #[test]
    fn empty() {
        assert_eq!(
            RevlogManifest::parse(None, &HgParents::None, b"").unwrap(),
            RevlogManifest {
                repo: None,
                parents: HgParents::None,
                content: ManifestContent {
                    files: BTreeMap::new(),
                },
            }
        );
    }

    #[test]
    fn bad_nonil() {
        match RevlogManifest::parse(None, &HgParents::None, b"hello123") {
            Ok(m) => panic!("unexpected manifest {:?}", m),
            Err(e) => println!("got expected error: {}", e),
        }
    }

    #[test]
    fn bad_nohash() {
        match RevlogManifest::parse(None, &HgParents::None, b"hello123\0") {
            Ok(m) => panic!("unexpected manifest {:?}", m),
            Err(e) => println!("got expected error: {}", e),
        }
    }

    #[test]
    fn bad_badhash1() {
        match RevlogManifest::parse(None, &HgParents::None, b"hello123\0abc123") {
            Ok(m) => panic!("unexpected manifest {:?}", m),
            Err(e) => println!("got expected error: {}", e),
        }
    }

    #[test]
    fn good_one() {
        match RevlogManifest::parse(
            None,
            &HgParents::One(THREES_HASH),
            b"hello123\0da39a3ee5e6b4b0d3255bfef95601890afd80709xltZZZ\n",
        ) {
            Ok(m) => {
                assert_eq!(m.parents(), &HgParents::One(THREES_HASH));
                let expect = vec![(
                    MPath::new(b"hello123").unwrap(),
                    Details {
                        hash: "da39a3ee5e6b4b0d3255bfef95601890afd80709".parse().unwrap(),
                        flag: Type::File(FileType::Symlink),
                    },
                )];
                assert_eq!(m.content.files.into_iter().collect::<Vec<_>>(), expect);
            }
            Err(e) => println!("got expected error: {}", e),
        }
    }

    #[test]
    fn one_roundtrip() {
        // Only one flag because its unclear how multiple flags should be ordered
        const RAW: &[u8] = b"hello123\0da39a3ee5e6b4b0d3255bfef95601890afd80709x\n";
        let m = RevlogManifest::parse(None, &HgParents::None, RAW).expect("failed to parse");

        let mut out = Vec::new();
        m.generate(&mut out).expect("generate failed");

        if RAW != &out[..] {
            println!("\nRAW: {:?}", str::from_utf8(RAW));
            println!("out: {:?}", str::from_utf8(out.as_ref()));
            panic!(
                "out ({} bytes) mismatch RAW ({} bytes)",
                RAW.len(),
                out.len()
            );
        }

        assert_eq!(m.parents(), &HgParents::None);
    }

    const MANIFEST: &[u8] = include_bytes!("flatmanifest.bin");

    #[test]
    fn fullmanifest() {
        match RevlogManifest::parse(None, &HgParents::Two(ONES_HASH, TWOS_HASH), MANIFEST) {
            Ok(m) => {
                println!("Got manifest:");
                for (k, v) in &m.content.files {
                    println!("{:?} {:?}", k, v);
                }
                assert_eq!(m.parents(), &HgParents::Two(ONES_HASH, TWOS_HASH));
            }
            Err(e) => panic!("Failed to load manifest: {}", e),
        }
    }

    #[test]
    fn roundtrip() {
        let m = RevlogManifest::parse(None, &HgParents::One(ONES_HASH), MANIFEST)
            .expect("parse failed");

        let mut out = Vec::new();
        m.generate(&mut out).expect("generate failed");

        if MANIFEST != &out[..] {
            panic!(
                "out ({} bytes) mismatch MANIFEST ({} bytes)",
                MANIFEST.len(),
                out.len()
            )
        }

        assert_eq!(m.parents(), &HgParents::One(ONES_HASH));
    }
}


/*
 * 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.
 */

// Support for lz4revlog

use nom::IResult;

use super::parser::detach_result;
use super::parser::Error;

pub fn lz4_decompress<'a, P, R: 'a>(i: &'a [u8], parse: P) -> IResult<&'a [u8], R, Error>
where
    for<'p> P: Fn(&'p [u8]) -> IResult<&'p [u8], R, Error>,
{
    // A previous implementation of lz4 decompress returned a remaining set of data, but the
    // implementation was incorrect and remains was always an empty byte array. On top of that
    // LZ4 compression doesn't have the concept of knowing when it's decompression stream ends,
    // so the concept of 'remaining' doesn't exist for LZ4 unless we enrich the data stream - which
    // wasn't happening. This is left here to remain a compatible interface with ZSTD compression
    // which does use remains for detach_result.
    let remains: &[u8] = &[];

    match lz4_pyframe::decompress(i) {
        Ok(decompressed) => detach_result(parse(&decompressed[..]), remains),
        Err(_err) => IResult::Error(nom::Err::Code(nom::ErrorKind::Custom(
            super::parser::Badness::BadLZ4,
        ))),
    }
}


/*
 * 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::collections::BTreeMap;
use std::collections::HashMap;
use std::collections::HashSet;
use std::fmt::Debug;
use std::fs::File;
use std::io;
use std::path::Path;
use std::sync::Arc;

use anyhow::bail;
use anyhow::format_err;
use anyhow::Context;
use anyhow::Result;
use bytes::Bytes;
use memmap2::Mmap;
pub use mercurial_types::bdiff;
pub use mercurial_types::bdiff::Delta;
pub use mercurial_types::delta;
pub use mercurial_types::HgBlob;
pub use mercurial_types::HgBlobNode;
pub use mercurial_types::HgEntryId;
pub use mercurial_types::HgNodeHash;
pub use mercurial_types::HgParents;
use nom::IResult;

use crate::errors::ErrorKind;

// Submodules
mod lz4;
mod parser;
mod revidx;

#[cfg(test)]
mod test;

pub use self::parser::Entry;
use self::parser::Header;
use self::parser::IdxFlags;
use self::parser::Version;
pub use self::revidx::RevIdx;

#[derive(Debug)]
enum Datafile {
    Loaded(Vec<u8>),
    Mmap(Mmap),
}

impl Datafile {
    fn map<P: AsRef<Path>>(path: P) -> io::Result<Datafile> {
        let file = File::open(path)?;
        unsafe { Mmap::map(&file).map(Datafile::Mmap) }
    }

    fn as_slice(&self) -> &[u8] {
        match *self {
            Datafile::Loaded(ref data) => data.as_ref(),
            Datafile::Mmap(ref mmap) => mmap.as_ref(),
        }
    }
}

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

fn map_io<T, F, R, E>(v: Option<T>, f: &mut F) -> Result<Option<R>, E>
where
    F: FnMut(T) -> Result<R, E>,
{
    match v {
        None => Ok(None),
        Some(v) => f(v).map(Option::Some),
    }
}

/// `Revlog` represents a Mercurial revlog structure
///
/// A Mercurial revlog logicically consists of two parts: an index containing metadata about each
/// revision in the file, and data about each one. These may be stored in one or two files,
/// depending on whether the data is inlined into the index or not.
///
/// This type represents the logical revlog. It allows iteration over the entries, fetching
/// entries at random, and extracting the data for each entry.
#[derive(Debug, Clone)]
pub struct Revlog {
    inner: Arc<RevlogInner>,
}

#[derive(Debug)]
struct RevlogInner {
    header: Header,
    idx: Datafile,
    data: Option<Datafile>,
    idxoff: BTreeMap<RevIdx, usize>,      // cache of index -> offset
    nodeidx: HashMap<HgNodeHash, RevIdx>, // cache of nodeid -> index
}

impl PartialEq<Self> for Revlog {
    fn eq(&self, other: &Self) -> bool {
        Arc::ptr_eq(&self.inner, &other.inner)
    }
}
impl Eq for Revlog {}

impl Revlog {
    fn init(idx: Datafile, data: Option<Datafile>) -> Result<Self> {
        let hdr = match parser::header(idx.as_slice()) {
            IResult::Done(_, hdr) => hdr,
            err => return Err(ErrorKind::Revlog(format!("Header parse failed: {:?}", err)).into()),
        };

        let mut data = data;
        if hdr.features.contains(parser::Features::INLINE) {
            data = None
        }

        let mut idxoff = BTreeMap::new();
        let mut nodeidx = HashMap::new();

        let mut inner = RevlogInner {
            header: hdr,
            idx,
            data,
            idxoff: BTreeMap::new(),
            nodeidx: HashMap::new(),
        };

        let mut off = 0;
        let mut i = RevIdx::zero();
        loop {
            let ent = inner.parse_entry(off);
            if let Ok(entry) = ent {
                idxoff.insert(i, off);
                nodeidx.insert(entry.nodeid, i);
                i = i.succ();
                off += inner.entry_size(Some(&entry));
            } else {
                break;
            }
        }
        inner.idxoff = idxoff;
        inner.nodeidx = nodeidx;
        Ok(Revlog {
            inner: Arc::new(inner),
        })
    }

    /// Construct a `Revlog` using in-memory data. The index is required; the data
    /// may not be if either its inlined into the data, or not required for operations.
    pub fn new(idx: Vec<u8>, data: Option<Vec<u8>>) -> Result<Self> {
        Self::init(Datafile::Loaded(idx), data.map(Datafile::Loaded))
    }

    /// Construct a `Revlog` from an index file at the given path. Data may be inlined
    /// not not required.
    pub fn from_idx_no_data<IP>(idxpath: IP) -> Result<Revlog>
    where
        IP: AsRef<Path>,
    {
        let idx = Datafile::map(idxpath).context("Can't map idxpath")?;

        let revlog = Revlog::init(idx, None)?;

        Ok(revlog)
    }

    /// Construct a `Revlog` from an index file and data file. If `datapath` is not provided
    /// (`None`), and the index file is not inlined, then it will replace the index file's
    /// extension with `.d` and attempt to open that. The operation will fail if that file can't
    /// be opened.
    pub fn from_idx_with_data<IP, DP>(idxpath: IP, datapath: Option<DP>) -> Result<Revlog>
    where
        IP: AsRef<Path> + Debug,
        DP: AsRef<Path> + Debug,
    {
        let mut revlog = Self::from_idx_no_data(&idxpath)
            .with_context(|| format!("Can't open index {:?}", idxpath))?;
        let datapath = datapath.as_ref().map(DP::as_ref);
        let idxpath = idxpath.as_ref();

        if !revlog.inner.have_data() {
            let datafile = match datapath {
                None => {
                    let path = idxpath.with_extension("d");
                    Datafile::map(&path)
                        .with_context(|| format!("Can't open data file {:?}", path))?
                }
                Some(path) => Datafile::map(path)
                    .with_context(|| format!("Can't open data file {:?}", path))?,
            };
            Arc::get_mut(&mut revlog.inner).unwrap().data = Some(datafile);
        }

        Ok(revlog)
    }

    pub fn index_entry_size(&self) -> usize {
        self.inner.fixed_entry_size()
    }

    /// Return `true` if the `Revlog` has the data it requires - ie, the data is either inlined,
    /// or a data file has been provided.
    pub fn have_data(&self) -> bool {
        self.inner.have_data()
    }

    /// Get the `Revlog`s header.
    pub fn get_header(&self) -> Header {
        self.inner.header
    }

    /// Return an `Entry` entry from the `RevIdx`.
    pub fn get_entry(&self, idx: RevIdx) -> Result<Entry> {
        self.inner.get_entry(idx)
    }

    /// Return an `Entry` entry from the `RevIdx`.
    pub fn is_ext_by_nodeid(&self, nodeid: HgNodeHash) -> Result<bool> {
        self.inner.is_ext_by_nodeid(nodeid)
    }

    /// Return the ordinal index of an entry with the given nodeid.
    pub fn get_idx_by_nodeid(&self, nodeid: HgNodeHash) -> Result<RevIdx> {
        self.inner.get_idx_by_nodeid(nodeid)
    }

    /// Return the ordinal index of an entry with the given nodeid.
    pub fn get_entry_by_id(&self, entryid: HgEntryId) -> Result<Entry> {
        let nodeid = entryid.clone().into_nodehash();
        self.inner.get_entry_by_nodeid(nodeid)
    }

    /// Return a `Chunk` for a revision at `RevIdx`.
    ///
    /// A `Chunk` either represents the literal
    /// text of the change, or a series of deltas against a previous version; the exact
    /// mechanism of applying the deltas depends on whether the `RevLog` has the `GENERAL_DELTA`
    /// flag set or not.
    pub fn get_chunk(&self, idx: RevIdx) -> Result<Chunk> {
        self.inner.get_chunk(idx)
    }

    pub fn get_rev(&self, tgtidx: RevIdx) -> Result<HgBlobNode> {
        self.inner.get_rev(tgtidx)
    }

    pub fn get_rev_by_nodeid(&self, id: HgNodeHash) -> Result<HgBlobNode> {
        self.inner.get_rev_by_nodeid(id)
    }

    pub fn get_rev_parents_by_nodeid(&self, id: HgNodeHash) -> Result<HgParents> {
        self.inner.get_rev_parents_by_nodeid(id)
    }

    /// Return the set of head revisions in a revlog
    pub fn get_heads(&self) -> Result<HashSet<HgNodeHash>> {
        self.inner.get_heads()
    }
}

impl RevlogInner {
    // Parse an entry at an offset, doing the correction for the overlap of the first
    // entry and the header.
    fn parse_entry(&self, off: usize) -> Result<Entry> {
        let res = match self.header.version {
            Version::Revlog0 => parser::index0(&self.idx.as_slice()[off..]),
            Version::RevlogNG => parser::indexng(&self.idx.as_slice()[off..]),
        };

        match res {
            IResult::Done(_, mut res) => {
                if off == 0 {
                    res.offset &= 0xffff;
                }
                Ok(res)
            }
            err => Err(ErrorKind::Revlog(format!(
                "failed to parse entry offset {}: {:?}",
                off, err
            ))
            .into()),
        }
    }

    fn fixed_entry_size(&self) -> usize {
        match self.header.version {
            Version::Revlog0 => parser::index0_size(),
            Version::RevlogNG => parser::indexng_size(),
        }
    }

    fn entry_size(&self, ent: Option<&Entry>) -> usize {
        let mut sz = self.fixed_entry_size();
        if self.header.features.contains(parser::Features::INLINE) {
            sz += ent.expect("inline needs ent").compressed_len as usize;
        }
        sz
    }

    fn offset_for_idx(&self, idx: RevIdx) -> Option<usize> {
        if self.header.features.contains(parser::Features::INLINE) {
            self.idxoff.get(&idx).cloned()
        } else {
            Some(idx * self.entry_size(None))
        }
    }

    fn have_data(&self) -> bool {
        // inline implies no data
        assert!(!self.header.features.contains(parser::Features::INLINE) || self.data.is_none());

        // have data if inline or data is non-None
        self.header.features.contains(parser::Features::INLINE) || self.data.is_some()
    }

    /// Return an `Entry` entry from the `RevIdx`.
    fn get_entry(&self, idx: RevIdx) -> Result<Entry> {
        if let Some(off) = self.offset_for_idx(idx) {
            // cache hit or computed
            self.parse_entry(off)
        } else {
            Err(ErrorKind::Revlog(format!("rev {:?} not found", idx)).into())
        }
    }

    // Return an `Entry` entry from the `RevIdx`.
    fn is_ext_by_nodeid(&self, nodeid: HgNodeHash) -> Result<bool> {
        self.get_entry_by_nodeid(nodeid)
            .map(|entry| entry.flags.contains(IdxFlags::EXTSTORED))
    }

    /// Return the ordinal index of an entry with the given nodeid.
    fn get_idx_by_nodeid(&self, nodeid: HgNodeHash) -> Result<RevIdx> {
        match self.nodeidx.get(&nodeid).cloned() {
            Some(idx) => Ok(idx), // cache hit
            None => Err(ErrorKind::Revlog(format!("nodeid {} not found", nodeid)).into()),
        }
    }

    fn get_entry_by_nodeid(&self, nodeid: HgNodeHash) -> Result<Entry> {
        self.get_idx_by_nodeid(nodeid)
            .and_then(|idx| self.get_entry(idx))
    }

    /// Return a `Chunk` for a revision at `RevIdx`.
    ///
    /// A `Chunk` either represents the literal
    /// text of the change, or a series of deltas against a previous version; the exact
    /// mechanism of applying the deltas depends on whether the `RevLog` has the `GENERAL_DELTA`
    /// flag set or not.
    fn get_chunk(&self, idx: RevIdx) -> Result<Chunk> {
        if !self.have_data() {
            bail!("Can't get chunks without data");
        }

        let entry = self.get_entry(idx)?;

        let (chunkdata, start) = if self.header.features.contains(parser::Features::INLINE) {
            let off = self.offset_for_idx(idx).expect("not cached?");
            let start = off + self.fixed_entry_size();

            (self.idx.as_slice(), start)
        } else {
            let start = entry.offset as usize;

            (
                self.data
                    .as_ref()
                    .expect("non-inline has no data")
                    .as_slice(),
                start,
            )
        };
        let end = start + (entry.compressed_len as usize);
        let chunkdata = &chunkdata[start..end];

        // If the entry has baserev that is equal to it's idx, then the chunk is literal data.
        // Otherwise its 0 or more deltas against the baserev. If its general delta, then the
        // baserev itself might also be delta, otherwise its all the deltas from baserev..idx.
        if Some(idx) == entry.baserev {
            if chunkdata.is_empty() {
                Ok(Chunk::Literal(vec![]))
            } else {
                match parser::literal(chunkdata) {
                    IResult::Done(rest, _) if !rest.is_empty() => Err(ErrorKind::Revlog(format!(
                        "Failed to unpack literal: {} remains, {:?}",
                        rest.len(),
                        &rest[..16]
                    ))
                    .into()),
                    IResult::Done(_, literal) => Ok(Chunk::Literal(literal)),
                    err => Err(
                        ErrorKind::Revlog(format!("Failed to unpack literal: {:?}", err)).into(),
                    ),
                }
            }
        } else {
            match parser::deltachunk(chunkdata) {
                IResult::Done(rest, _) if !rest.is_empty() => Err(ErrorKind::Revlog(format!(
                    "Failed to unpack details: {} remains, {:?}",
                    rest.len(),
                    &rest[..16]
                ))
                .into()),
                IResult::Done(_, deltas) => Ok(Chunk::Deltas(deltas)),
                err => Err(ErrorKind::Revlog(format!("Failed to unpack deltas: {:?}", err)).into()),
            }
        }
    }

    fn is_general_delta(&self) -> bool {
        self.header
            .features
            .contains(parser::Features::GENERAL_DELTA)
    }

    fn construct_simple(&self, tgtidx: RevIdx) -> Result<Vec<u8>> {
        assert!(!self.is_general_delta());

        let entry = self.get_entry(tgtidx)?;

        // if there's no baserev, then use the target as a baserev (it should be literal)
        let baserev = entry.baserev.map_or(tgtidx, Into::into);

        // XXX: Fix this to use delta::Delta instead of bdiff::Delta.

        // non-general delta - baserev should be literal, then we applying
        // each delta up to idx
        let mut data = Vec::new();
        let mut chain = Vec::new();
        for idx in baserev.range_to(tgtidx.succ()) {
            let chunk = self
                .get_chunk(idx)
                .with_context(|| format_err!("simple tgtidx {:?} idx {:?}", tgtidx, idx));

            match chunk? {
                Chunk::Literal(v) => {
                    data = v;
                    chain.clear();
                }
                Chunk::Deltas(deltas) => {
                    chain.push(deltas);
                }
            }
        }

        delta::compat::apply_deltas(data.as_ref(), chain)
    }

    fn construct_general(&self, tgtidx: RevIdx) -> Result<Vec<u8>> {
        assert!(self.is_general_delta());

        let mut chunks = Vec::new();
        let mut idx = tgtidx;

        // general delta - walk backwards until we hit a literal, collecting chunks on the way
        let data = loop {
            chunks.push(idx);

            let chunk = self.get_chunk(idx).with_context(|| {
                format_err!("construct_general tgtidx {:?} idx {:?}", tgtidx, idx)
            })?;

            // We have three valid cases:
            // 1) Literal chunk - this is possible only if baserev == idx
            // 2) Delta against empty string - this is possible only if baserev is None.
            //    In core hg it matches a case where baserev == -1.
            // 3) Delta against non-empty string. Only if baserev is Some(...) and baserev < idx.
            match self.get_entry(idx)?.baserev {
                Some(baseidx) if idx == baseidx => {
                    // This is a literal
                    match chunk {
                        Chunk::Literal(v) => {
                            chunks.pop();
                            break v;
                        }
                        _ => {
                            Err(ErrorKind::Revlog("expected a literal".to_string()))?;
                        }
                    }
                }
                Some(baseidx) if idx > baseidx => {
                    idx = baseidx;
                }
                Some(baseidx) => {
                    Err(ErrorKind::Revlog(format!(
                        "baserev {:?} >= idx {:?}",
                        baseidx, idx
                    )))?;
                }
                None => match chunk {
                    // This is a delta against "-1" revision i.e. empty revision
                    Chunk::Deltas(_) => {
                        break vec![];
                    }
                    _ => {
                        Err(ErrorKind::Revlog(
                            "expected a delta against empty string".to_string(),
                        ))?;
                    }
                },
            }
        };

        // XXX: Fix this to use delta::Delta instead of bdiff::Delta.
        let chain = chunks.into_iter().rev().map(|idx| {
            let chunk = self.get_chunk(idx);

            match chunk {
                Ok(Chunk::Deltas(deltas)) => deltas,
                _ => panic!("Literal text found in delta chain."),
            }
        });

        delta::compat::apply_deltas(data.as_ref(), chain)
    }

    fn parse_parents(&self, entry: &Entry) -> Result<(Option<HgNodeHash>, Option<HgNodeHash>)> {
        let mut pnodeid = |p| {
            let pn = self.get_entry(p);
            pn.map(|n| n.nodeid)
        };
        let p1 = map_io(entry.p1, &mut pnodeid)?;
        let p2 = map_io(entry.p2, &mut pnodeid)?;
        Ok((p1, p2))
    }

    fn make_node(&self, entry: &Entry, blob: HgBlob) -> Result<HgBlobNode> {
        let (p1, p2) = self.parse_parents(entry)?;

        Ok(HgBlobNode::new(blob, p1, p2))
    }

    fn get_rev(&self, tgtidx: RevIdx) -> Result<HgBlobNode> {
        if !self.have_data() {
            bail!("Need data to assemble revision");
        }

        let entry = self.get_entry(tgtidx)?;

        let data = if self.is_general_delta() {
            self.construct_general(tgtidx)?
        } else {
            self.construct_simple(tgtidx)?
        };

        self.make_node(&entry, HgBlob::from(Bytes::from(data)))
    }

    fn get_parents(&self, tgtidx: RevIdx) -> Result<HgParents> {
        let entry = self.get_entry(tgtidx)?;
        let (p1, p2) = self.parse_parents(&entry)?;

        Ok(HgParents::new(p1, p2))
    }

    fn get_rev_by_nodeid(&self, id: HgNodeHash) -> Result<HgBlobNode> {
        self.get_idx_by_nodeid(id).and_then(move |idx| {
            self.get_rev(idx)
                .with_context(|| format!("can't get rev for id {}", id))
        })
    }

    fn get_rev_parents_by_nodeid(&self, id: HgNodeHash) -> Result<HgParents> {
        self.get_idx_by_nodeid(id).and_then(move |idx| {
            self.get_parents(idx)
                .with_context(|| format!("can't get parents for id {}", id))
        })
    }

    /// Return the set of head revisions in a revlog
    fn get_heads(&self) -> Result<HashSet<HgNodeHash>> {
        // Current set of candidate heads
        let mut heads = HashMap::new();

        for (idx, entry) in self.into_iter() {
            // New entry could be a head
            heads.insert(idx, entry);

            // This entry's parent(s) are non-heads
            if let Some(p1) = entry.p1 {
                let _ = heads.remove(&p1);
            }

            if let Some(p2) = entry.p2 {
                let _ = heads.remove(&p2);
            }
        }

        // Convert to a set of nodeids
        Ok(heads.values().map(|n| n.nodeid).collect())
    }
}

/// Data associated with a revision.
///
/// XXX internal detail?
#[derive(Debug)]
pub enum Chunk {
    /// Literal text of the revision
    Literal(Vec<u8>),
    /// Vector of `Delta`s against a previous version
    Deltas(Vec<Delta>),
}

struct RevlogInnerIter<'a>(&'a RevlogInner, RevIdx);

impl<'a> IntoIterator for &'a RevlogInner {
    type Item = <Self::IntoIter as Iterator>::Item;
    type IntoIter = RevlogInnerIter<'a>;

    fn into_iter(self) -> Self::IntoIter {
        RevlogInnerIter(self, RevIdx::zero())
    }
}

impl<'a> Iterator for RevlogInnerIter<'a> {
    type Item = (RevIdx, Entry);

    fn next(&mut self) -> Option<Self::Item> {
        let idx = self.1;
        let ret = self.0.get_entry(idx).ok();
        self.1 = idx.succ();
        ret.map(|r| (idx, r))
    }
}

#[derive(Debug)]
pub struct RevlogIter(Arc<RevlogInner>, RevIdx);

impl IntoIterator for Revlog {
    type Item = <Self::IntoIter as Iterator>::Item;
    type IntoIter = RevlogIter;

    fn into_iter(self) -> Self::IntoIter {
        RevlogIter(self.inner, RevIdx::zero())
    }
}

impl<'a> IntoIterator for &'a Revlog {
    type Item = <Self::IntoIter as Iterator>::Item;
    type IntoIter = RevlogIter;

    fn into_iter(self) -> Self::IntoIter {
        RevlogIter(self.inner.clone(), RevIdx::zero())
    }
}

impl RevlogIter {
    pub fn seek(&mut self, idx: RevIdx) {
        self.1 = idx;
    }
}

impl Iterator for RevlogIter {
    type Item = (RevIdx, Entry);

    fn next(&mut self) -> Option<Self::Item> {
        let revlog = &self.0;

        let idx = self.1;
        let ret = revlog.get_entry(idx).ok();
        self.1 = idx.succ();
        ret.map(|r| (idx, r))
    }
}


/*
 * 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.
 */

// Nom parser for Mercurial revlogs

use std::fmt::Debug;
use std::io::Read;

use bitflags::bitflags;
use flate2::read::ZlibDecoder;
use mercurial_types::bdiff::Delta;
use mercurial_types::HgNodeHash;
use nom::be_u16;
use nom::be_u32;
use nom::Err;
use nom::ErrorKind;
use nom::IResult;
use nom::Needed;
use nom::*;

use super::lz4;
use crate::revlog::revidx::RevIdx;

// #[derive(Copy, Clone, Debug, Eq, PartialEq)]
// pub enum Badness {
// IO,
// Version,
// Features,
// BadZlib,
// }
//

pub type Error = u32;

// hack until I work out how to propagate proper E type
#[allow(non_upper_case_globals, non_snake_case, dead_code)]
pub mod Badness {
    use super::Error;
    pub const IO: Error = 0;
    pub const Version: Error = 1;
    pub const Features: Error = 2;
    pub const BadZlib: Error = 3;
    pub const BadLZ4: Error = 4;
}

// `Revlog` features
bitflags! {
    pub struct Features: u16 {
        const INLINE        = 1 << 0;
        const GENERAL_DELTA = 1 << 1;
    }
}

// Per-revision flags
bitflags! {
    pub struct IdxFlags: u16 {
        const OCTOPUS_MERGE = 1 << 12;
        const EXTSTORED     = 1 << 13;
        const CENSORED      = 1 << 15;
    }
}

/// Revlog version number
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Version {
    Revlog0 = 0,
    RevlogNG = 1,
}

/// Revlog header
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Header {
    pub version: Version,
    pub features: Features,
}

/// Entry entry for a revision
#[derive(Copy, Clone, Debug)]
pub struct Entry {
    pub offset: u64,         // offset of content (delta/literal) in datafile (or inlined)
    pub flags: IdxFlags,     // unused?
    pub compressed_len: u32, // compressed content size
    pub len: Option<u32>,    // size of final file (after applying deltas)
    pub baserev: Option<RevIdx>, // base/previous rev for deltas (None if literal)
    pub linkrev: RevIdx,     // changeset id
    pub p1: Option<RevIdx>,  // parent p1
    pub p2: Option<RevIdx>,  // parent p2
    pub nodeid: HgNodeHash,  // nodeid
}

impl Entry {
    pub fn nodeid(&self) -> HgNodeHash {
        self.nodeid
    }
}

// Parse the revlog header
named!(pub header<Header>,
    do_parse!(
        features: return_error!(ErrorKind::Custom(Badness::IO), be_u16) >>
        version: return_error!(ErrorKind::Custom(Badness::IO), be_u16) >>
        ({
            let vers = match version {
                0 => Version::Revlog0,
                1 => Version::RevlogNG,
                _ => panic!("bad version"),
            };

            let features = match Features::from_bits(features) {
                Some(f) => f,
                None => panic!("bad features"),
            };

            Header {
                version: vers,
                features,
            }
        }))
);

pub fn indexng_size() -> usize {
    6 + 2 + 4 + 4 + 4 + 4 + 4 + 4 + 32
}

// Parse an "NG" revlog entry
named!(pub indexng<Entry>,
    do_parse!(
        offset: return_error!(ErrorKind::Custom(Badness::IO), be_u48) >>    // XXX if first, then only 2 bytes, implied 0 in top 4
        flags: return_error!(ErrorKind::Custom(Badness::IO), be_u16) >>     // ?
        compressed_length: return_error!(ErrorKind::Custom(Badness::IO), be_u32) >>
        uncompressed_length: return_error!(ErrorKind::Custom(Badness::IO), be_u32) >>
        baserev: return_error!(ErrorKind::Custom(Badness::IO), be_u32) >>
        linkrev: return_error!(ErrorKind::Custom(Badness::IO), be_u32) >>
        p1: return_error!(ErrorKind::Custom(Badness::IO), be_u32) >>
        p2: return_error!(ErrorKind::Custom(Badness::IO), be_u32) >>
        hash: take!(32) >>
        ({
            Entry {
                offset,
                flags: IdxFlags::from_bits(flags).expect("bad rev idx flags"),
                compressed_len: compressed_length,
                len: Some(uncompressed_length),
                baserev: if baserev == !0 { None } else { Some(baserev.into()) },
                linkrev: linkrev.into(),
                p1: if p1 == !0 { None } else { Some(p1.into()) },
                p2: if p2 == !0 { None } else { Some(p2.into()) },
                nodeid: HgNodeHash::from_bytes(&hash[..20]).expect("bad bytes for sha"),
            }
        })
    )
);

pub fn index0_size() -> usize {
    4 + 4 + 4 + 4 + 4 + 4 + 4 + 20
}

// Parse an original revlog entry
named!(pub index0<Entry>,
    do_parse!(
        _header: header >>
        offset: return_error!(ErrorKind::Custom(Badness::IO), be_u32) >>
        compressed_length: return_error!(ErrorKind::Custom(Badness::IO), be_u32) >>
        baserev: return_error!(ErrorKind::Custom(Badness::IO), be_u32) >>
        linkrev: return_error!(ErrorKind::Custom(Badness::IO), be_u32) >>
        p1: return_error!(ErrorKind::Custom(Badness::IO), be_u32) >>
        p2: return_error!(ErrorKind::Custom(Badness::IO), be_u32) >>
        hash: take!(20) >>
        ({
            Entry {
                offset: offset as u64,
                flags: IdxFlags::empty(),
                compressed_len: compressed_length,
                len: None,
                baserev: if baserev == !0 { None } else { Some(baserev.into()) },
                linkrev: linkrev.into(),
                p1: if p1 == !0 { None } else { Some(p1.into()) },
                p2: if p2 == !0 { None } else { Some(p2.into()) },
                nodeid: HgNodeHash::from_bytes(&hash[..20]).expect("bad bytes for sha"),
            }
        })
    )
);

// Parse a single Delta
named!(pub delta<Delta>,
    do_parse!(
        start: be_u32 >>
        end: be_u32 >>
        content: length_bytes!(be_u32) >>
        ({
            Delta {
                start: start as usize,
                end: end as usize,
                content: content.into(),
            }
        })
    )
);

// Parse 0 or more deltas
named!(deltas<Vec<Delta>>, many0!(delta));

// A chunk of data data that contains some Deltas; the caller defines the framing bytes
// bounding the input.
named!(pub deltachunk<Vec<Delta> >,
    map!(
        many0!(
            alt!(
                do_parse!(tag!(b"u") >> d: deltas >> (d)) |                                  // uncompressed with explicit 'u' header
                do_parse!(peek!(tag!(b"\0")) >> d: deltas >> (d)) |                          // uncompressed with included initial 0x00
                do_parse!(peek!(tag!(b"x")) >> d: apply!(zlib_decompress, deltas) >> (d)) |  // compressed; 'x' part of the zlib stream
                do_parse!(tag!(b"4") >> d: apply!(lz4::lz4_decompress, deltas) >> (d))       // compressed w/ lz4
            )
        ),
        |dv: Vec<_>| dv.into_iter().flatten().collect())
);

fn remains(i: &[u8]) -> IResult<&[u8], &[u8]> {
    IResult::Done(&i[..0], i)
}

named!(remains_owned<Vec<u8>>, map!(remains, |x: &[u8]| x.into()));

// Parse some literal data, possibly compressed
named!(pub literal<Vec<u8> >,
    alt!(
        do_parse!(peek!(tag!(b"\0")) >> d: remains >> (d.into())) |
        do_parse!(peek!(tag!(b"x")) >> d: apply!(zlib_decompress, remains_owned) >> (d)) |
        do_parse!(tag!(b"4") >> d: apply!(lz4::lz4_decompress, remains_owned) >> (d)) |
        do_parse!(tag!(b"u") >> d: remains >> (d.into()))
    )
);

// Remap error to remove reference to `data`
pub fn detach_result<'inp, 'out, O: 'out, E: 'out>(
    res: IResult<&'inp [u8], O, E>,
    rest: &'out [u8],
) -> IResult<&'out [u8], O, E> {
    match res {
        IResult::Done(_, o) => IResult::Done(rest, o),
        IResult::Incomplete(n) => IResult::Incomplete(n),
        IResult::Error(Err::Code(e))
        | IResult::Error(Err::Node(e, _))
        | IResult::Error(Err::Position(e, _))
        | IResult::Error(Err::NodePosition(e, ..)) => IResult::Error(Err::Code(e)),
    }
}

/// Unpack a chunk of data and apply a parse function to the output.
fn zlib_decompress<'a, P, R: 'a, E: Debug + 'a>(i: &'a [u8], parse: P) -> IResult<&'a [u8], R, E>
where
    for<'p> P: Fn(&'p [u8]) -> IResult<&'p [u8], R, E>,
{
    let mut data = Vec::new();

    let inused = {
        let mut zdec = ZlibDecoder::new(i);

        match zdec.read_to_end(&mut data) {
            Ok(_) => zdec.total_in() as usize,
            Err(err) => panic!("zdec failed {:?}", err),
        }
    };

    let remains = &i[inused..];

    detach_result(parse(&data[..]), remains)
}

/// Parse a 6 byte big-endian offset
#[inline]
#[allow(clippy::identity_op)]
fn be_u48(i: &[u8]) -> IResult<&[u8], u64> {
    if i.len() < 6 {
        IResult::Incomplete(Needed::Size(6))
    } else {
        let res = ((i[0] as u64) << 40)
            + ((i[1] as u64) << 32)
            + ((i[2] as u64) << 24)
            + ((i[3] as u64) << 16)
            + ((i[4] as u64) << 8)
            + ((i[5] as u64) << 0);
        IResult::Done(&i[6..], res)
    }
}

#[cfg(test)]
mod test {
    use nom::IResult;

    use super::header;
    use super::Features;
    use super::Header;
    use super::Version;

    #[test]
    fn test_header_0() {
        let d = [0x00, 0x00, 0x00, 0x00];
        assert_eq!(
            header(&d[..]),
            IResult::Done(
                &b""[..],
                Header {
                    version: Version::Revlog0,
                    features: Features::empty(),
                }
            )
        )
    }

    #[test]
    fn test_header_1() {
        let d = [0x00, 0x00, 0x00, 0x01];
        assert_eq!(
            header(&d[..]),
            IResult::Done(
                &b""[..],
                Header {
                    version: Version::RevlogNG,
                    features: Features::empty(),
                }
            )
        )
    }

    #[test]
    fn test_header_feat_1() {
        let d = [0x00, 0x01, 0x00, 0x01];
        assert_eq!(
            header(&d[..]),
            IResult::Done(
                &b""[..],
                Header {
                    version: Version::RevlogNG,
                    features: Features::INLINE,
                }
            )
        )
    }

    #[test]
    fn test_header_feat_2() {
        let d = [0x00, 0x02, 0x00, 0x01];
        assert_eq!(
            header(&d[..]),
            IResult::Done(
                &b""[..],
                Header {
                    version: Version::RevlogNG,
                    features: Features::GENERAL_DELTA,
                }
            )
        )
    }

    #[test]
    fn test_header_feat_3() {
        let d = [0x00, 0x03, 0x00, 0x01];
        assert_eq!(
            header(&d[..]),
            IResult::Done(
                &b""[..],
                Header {
                    version: Version::RevlogNG,
                    features: Features::INLINE | Features::GENERAL_DELTA,
                }
            )
        )
    }
}


/*
 * 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::ops::Add;
use std::ops::Mul;
use std::str::FromStr;
use std::u32;

/// Index into a `RevLog`
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct RevIdx(u32);

// Implement `RevIdx`s methods
impl RevIdx {
    /// Return index for first entry
    pub fn zero() -> Self {
        RevIdx(0)
    }

    /// Return successor index
    pub fn succ(self) -> Self {
        RevIdx(self.0 + 1)
    }

    /// Return previous index
    ///
    /// Panics if index is zero.
    pub fn pred(self) -> Self {
        assert!(self.0 > 0);
        RevIdx(self.0 - 1)
    }

    /// Return iterator for a range from index to `lim`.
    pub fn range_to(&self, lim: Self) -> RevIdxRange {
        RevIdxRange(self.0, lim.0)
    }

    /// Return an open ended iterator from index.
    pub fn range(&self) -> RevIdxRange {
        RevIdxRange(self.0, u32::MAX)
    }

    pub fn as_u32(&self) -> u32 {
        self.0
    }
}

// Construct a `RevIdx` from a `u32`
impl From<u32> for RevIdx {
    fn from(v: u32) -> Self {
        RevIdx(v)
    }
}

// Construct a `RevIdx` from a `usize`
// Panics if the usize is larger than u32::MAX
impl From<usize> for RevIdx {
    fn from(v: usize) -> Self {
        assert!(v <= u32::MAX as usize);
        RevIdx(v as u32)
    }
}

// Construct a `RevIdx` from a string (which may fail)
impl FromStr for RevIdx {
    type Err = <u32 as FromStr>::Err;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        u32::from_str(s).map(RevIdx)
    }
}

// Multiply operator for RevIdx * usize -> usize
// Used for constructing a byte offset for an index
impl Mul<usize> for RevIdx {
    type Output = usize;

    fn mul(self, other: usize) -> Self::Output {
        self.0 as usize * other
    }
}

// RevIdx + usize -> RevIdx
impl Add<usize> for RevIdx {
    type Output = RevIdx;

    fn add(self, other: usize) -> Self::Output {
        RevIdx((self.0 as usize + other) as u32)
    }
}

// Convert a `RevIdx` into an open-ended iterator of RevIdx values
// starting at RevIdx's value. ie, RevIdx(2).into_iter() => RevIdx(2), RevIdx(3), ...
impl<'a> IntoIterator for &'a RevIdx {
    type Item = RevIdx;
    type IntoIter = RevIdxRange;

    fn into_iter(self) -> Self::IntoIter {
        self.range()
    }
}

/// An open-ended or bounded iterator over a range of RevIdx
#[derive(Copy, Clone, Debug)]
pub struct RevIdxRange(u32, u32);

impl Iterator for RevIdxRange {
    type Item = RevIdx;

    fn next(&mut self) -> Option<Self::Item> {
        if self.0 < self.1 {
            let ret = RevIdx(self.0);
            self.0 += 1;
            Some(ret)
        } else {
            None
        }
    }
}

#[cfg(test)]
mod test {
    use std::str::FromStr;

    use super::*;

    #[test]
    fn zero() {
        assert_eq!(RevIdx::zero(), RevIdx(0))
    }

    #[test]
    fn succ() {
        assert_eq!(RevIdx::zero().succ(), RevIdx(1));
        assert_eq!(RevIdx::zero().succ().succ(), RevIdx(2));
    }

    #[test]
    fn pred() {
        assert_eq!(RevIdx(10).pred(), RevIdx(9));
    }

    #[test]
    #[should_panic]
    fn bad_pred() {
        println!("bad {:?}", RevIdx::zero().pred());
    }

    #[test]
    fn range_to() {
        let v: Vec<_> = RevIdx::zero().range_to(RevIdx(5)).collect();
        assert_eq!(
            v,
            vec![RevIdx(0), RevIdx(1), RevIdx(2), RevIdx(3), RevIdx(4)]
        );
    }

    #[test]
    fn iter() {
        let v: Vec<_> = RevIdx::zero().into_iter().take(5).collect();
        assert_eq!(
            v,
            vec![RevIdx(0), RevIdx(1), RevIdx(2), RevIdx(3), RevIdx(4)]
        )
    }

    #[test]
    fn fromstr() {
        let idx: RevIdx = FromStr::from_str("555").expect("Valid string");
        assert_eq!(idx, RevIdx(555));
    }

    #[test]
    fn fromstr_bad1() {
        match RevIdx::from_str("abc123") {
            Ok(x) => panic!("unexpected success with {:?}", x),
            Err(err) => println!("ok {:?}", err),
        }
    }

    #[test]
    fn fromstr_bad2() {
        match RevIdx::from_str("-1") {
            Ok(x) => panic!("unexpected success with {:?}", x),
            Err(err) => println!("ok {:?}", err),
        }
    }
}


/*
 * 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 super::*;

static EMPTY: &[u8] = include_bytes!("empty.i.bin");

#[test]
fn emptyrev() {
    let revlog = Revlog::new(EMPTY.to_vec(), None).expect("construction failed");
    let node = revlog
        .get_rev(RevIdx::from(0u32))
        .expect("failed to get rev");

    assert_eq!(node.size(), 0);
}


// @generated by autocargo
use std::env;
use std::fs;
use std::path::Path;

use thrift_compiler::Config;
use thrift_compiler::GenContext;

#[rustfmt::skip]
fn main() {
    // Rerun if this gets rewritten.
    println!("cargo:rerun-if-changed=thrift_build.rs");

    let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR env not provided");
    let out_dir: &Path = out_dir.as_ref();
    fs::write(
        out_dir.join("cratemap"),
        "mercurial_thrift crate
mononoke_types_thrift mononoke_types_thrift",
    ).expect("Failed to write cratemap");

    let conf = {
        let mut conf = Config::from_env(GenContext::Lib).expect("Failed to instantiate thrift_compiler::Config");

        let path_from_manifest_to_base: &Path = "../../../../..".as_ref();
        let cargo_manifest_dir =
            env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not provided");
        let cargo_manifest_dir: &Path = cargo_manifest_dir.as_ref();
        let base_path = cargo_manifest_dir
            .join(path_from_manifest_to_base)
            .canonicalize()
            .expect("Failed to canonicalize base_path");
        // TODO: replace canonicalize() with std::path::absolute() when
        // https://github.com/rust-lang/rust/pull/91673 is available (~Rust 1.60)
        // and remove this block.
        #[cfg(windows)]
        let base_path = Path::new(
            base_path
                .as_path()
                .to_string_lossy()
                .trim_start_matches(r"\\?\"),
            )
            .to_path_buf();

        conf.base_path(base_path);

        let options = "types_crate=mercurial-thrift__types";
        if !options.is_empty() {
            conf.options(options);
        }

        let include_srcs = vec![

        ];
        conf.include_srcs(include_srcs);

        conf
    };

    conf
        .run(&[
            "mercurial_thrift.thrift"
        ])
        .expect("Failed while running thrift compilation");
}


// @generated by autocargo
::codegen_includer_proc_macro::include!();


/*
 * 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 thiserror::Error;

#[derive(Debug, Error)]
pub enum ErrorKind {
    #[error("invalid manifest description: {0}")]
    InvalidManifestDescription(String),
    #[error("invalid path map: {0}")]
    InvalidPathMap(String),
    #[error("invalid directory hash map: {0}")]
    InvalidDirectoryHashes(String),
}