/*
 * 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::fmt;
use std::fmt::Display;
use std::io::Write;

use anyhow::bail;
use anyhow::Error;
use anyhow::Result;
use async_trait::async_trait;
use blobstore::Blobstore;
use blobstore::Loadable;
use blobstore::LoadableError;
use bytes::Bytes;
use context::CoreContext;
use mononoke_types::DateTime;

use super::revlog::serialize_extras;
use super::revlog::Extra;
use super::revlog::RevlogChangeset;
use crate::nodehash::HgChangesetId;
use crate::nodehash::HgManifestId;
use crate::HgBlobNode;
use crate::HgChangesetEnvelopeMut;
use crate::HgNodeHash;
use crate::HgParents;
use crate::MPath;

const STEP_PARENTS_METADATA_KEY: &str = "stepparents";

pub struct ChangesetMetadata {
    pub user: String,
    pub time: DateTime,
    pub extra: BTreeMap<Vec<u8>, Vec<u8>>,
    pub message: String,
}

impl ChangesetMetadata {
    pub fn record_step_parents(&mut self, step_parents: impl Iterator<Item = HgChangesetId>) {
        let mut meta = Vec::new();

        for (idx, parent) in step_parents.enumerate() {
            if idx > 0 {
                write!(meta, ",").expect("writes to memory don't fail");
            }
            write!(meta, "{}", parent).expect("writes to memory don't fail");
        }

        if meta.is_empty() {
            return;
        }

        self.extra.insert(STEP_PARENTS_METADATA_KEY.into(), meta);
    }

    pub fn record_committer(
        &mut self,
        committer: &str,
        committer_time: &DateTime,
    ) -> Result<(), Error> {
        let committer_key = "committer".as_bytes();
        if self.extra.contains_key(committer_key) {
            bail!("commiter extra is already set, can't insert another one!");
        }

        // Use the same format as hggit extension - https://fburl.com/diffusion/3ckf76fd
        let value = format!(
            "{} {} {}",
            committer,
            committer_time.timestamp_secs(),
            committer_time.tz_offset_secs()
        );

        self.extra
            .insert(committer_key.to_vec(), value.as_bytes().to_vec());

        Ok(())
    }
}

#[derive(Debug, Clone)]
pub struct HgChangesetContent {
    p1: Option<HgNodeHash>,
    p2: Option<HgNodeHash>,
    manifestid: HgManifestId,
    user: Vec<u8>,
    time: DateTime,
    extra: Extra,
    files: Vec<MPath>,
    message: Vec<u8>,
}

impl HgChangesetContent {
    pub fn new_from_parts(
        // XXX replace parents with p1 and p2
        parents: HgParents,
        manifestid: HgManifestId,
        cs_metadata: ChangesetMetadata,
        files: Vec<MPath>,
    ) -> Self {
        let (p1, p2) = parents.get_nodes();
        Self {
            p1,
            p2,
            manifestid,
            user: cs_metadata.user.into_bytes(),
            time: cs_metadata.time,
            extra: Extra::new(cs_metadata.extra),
            files,
            message: cs_metadata.message.into_bytes(),
        }
    }

    pub fn from_revlogcs(revlogcs: RevlogChangeset) -> Self {
        Self {
            p1: revlogcs.p1,
            p2: revlogcs.p2,
            manifestid: revlogcs.manifestid,
            user: revlogcs.user,
            time: revlogcs.time,
            extra: revlogcs.extra,
            files: revlogcs.files,
            message: revlogcs.message,
        }
    }

    pub fn compute_hash(&self) -> Result<HgChangesetId> {
        let mut v = Vec::new();

        self.generate(&mut v)?;
        let blobnode = HgBlobNode::new(Bytes::from(v), self.p1(), self.p2());

        let nodeid = blobnode.nodeid();
        Ok(HgChangesetId::new(nodeid))
    }

    /// Generate a serialized changeset. This is the counterpart to parse, and generates
    /// in the same format as Mercurial. It should be bit-for-bit identical in fact.
    fn generate<W: Write>(&self, out: &mut W) -> Result<()> {
        write!(out, "{}\n", self.manifestid.into_nodehash())?;
        out.write_all(&self.user)?;
        out.write_all(b"\n")?;
        write!(
            out,
            "{} {}",
            self.time.timestamp_secs(),
            self.time.tz_offset_secs()
        )?;

        if !self.extra.is_empty() {
            write!(out, " ")?;
            serialize_extras(&self.extra, out)?;
        }

        write!(out, "\n")?;
        for f in &self.files {
            write!(out, "{}\n", f)?;
        }
        write!(out, "\n")?;
        out.write_all(&self.message)?;

        Ok(())
    }

    #[inline]
    pub fn p1(&self) -> Option<HgNodeHash> {
        self.p1
    }

    #[inline]
    pub fn p2(&self) -> Option<HgNodeHash> {
        self.p2
    }
}

#[derive(Debug, Clone)]
pub struct HgBlobChangeset {
    changesetid: HgChangesetId, // redundant - can be computed from revlogcs?
    content: HgChangesetContent,
}

impl HgBlobChangeset {
    pub fn new(content: HgChangesetContent) -> Result<Self> {
        Ok(Self::new_with_id(content.compute_hash()?, content))
    }

    pub fn new_with_id(changesetid: HgChangesetId, content: HgChangesetContent) -> Self {
        Self {
            changesetid,
            content,
        }
    }

    pub fn get_changeset_id(&self) -> HgChangesetId {
        self.changesetid
    }

    pub async fn load<'a, B: Blobstore>(
        ctx: &'a CoreContext,
        blobstore: &'a B,
        changesetid: HgChangesetId,
    ) -> Result<Option<Self>> {
        let got = RevlogChangeset::load(ctx, blobstore, changesetid).await?;
        Ok(got.map(|revlogcs| {
            HgBlobChangeset::new_with_id(changesetid, HgChangesetContent::from_revlogcs(revlogcs))
        }))
    }

    pub async fn save<'a, B: Blobstore>(
        &'a self,
        ctx: &'a CoreContext,
        blobstore: &'a B,
    ) -> Result<()> {
        let key = self.changesetid.blobstore_key();

        let contents = {
            let mut v = Vec::new();

            self.content.generate(&mut v).map(|()| Bytes::from(v))?
        };

        let envelope = HgChangesetEnvelopeMut {
            node_id: self.changesetid,
            p1: self.content.p1().map(HgChangesetId::new),
            p2: self.content.p2().map(HgChangesetId::new),
            contents,
        };
        let envelope = envelope.freeze();
        let blob = envelope.into_blob();
        blobstore.put(ctx, key, blob.into()).await
    }

    #[inline]
    pub fn p1(&self) -> Option<HgNodeHash> {
        self.content.p1()
    }

    #[inline]
    pub fn p2(&self) -> Option<HgNodeHash> {
        self.content.p2()
    }

    pub fn manifestid(&self) -> HgManifestId {
        self.content.manifestid
    }

    pub fn user(&self) -> &[u8] {
        &self.content.user
    }

    pub fn extra(&self) -> &BTreeMap<Vec<u8>, Vec<u8>> {
        self.content.extra.as_ref()
    }

    pub fn message(&self) -> &[u8] {
        &self.content.message
    }

    pub fn files(&self) -> &[MPath] {
        &self.content.files
    }

    pub fn time(&self) -> &DateTime {
        &self.content.time
    }

    pub fn parents(&self) -> HgParents {
        // XXX Change this to return p1 and p2 directly.
        HgParents::new(self.content.p1(), self.content.p2())
    }

    pub fn step_parents(&self) -> Result<Vec<HgNodeHash>> {
        let mut ret = vec![];

        if let Some(step_parents) = self.extra().get(STEP_PARENTS_METADATA_KEY.as_bytes()) {
            let step_parents = std::str::from_utf8(step_parents)?;
            for csid in step_parents.split(',') {
                let csid = csid.parse()?;
                ret.push(csid);
            }
        }

        Ok(ret)
    }
}

#[async_trait]
impl Loadable for HgChangesetId {
    type Value = HgBlobChangeset;

    async fn load<'a, B: Blobstore>(
        &'a self,
        ctx: &'a CoreContext,
        blobstore: &'a B,
    ) -> Result<Self::Value, LoadableError> {
        let csid = *self;
        let value = HgBlobChangeset::load(ctx, blobstore, csid).await?;
        value.ok_or_else(|| LoadableError::Missing(csid.blobstore_key()))
    }
}

impl Display for HgBlobChangeset {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let message = self.message();
        let title_end = message
            .iter()
            .enumerate()
            .find(|(_, &c)| c == b'\n')
            .map_or(message.len(), |(i, _)| i);

        write!(
            f,
            "changeset: {}\nauthor: {}\ndate: {}\nsummary: {}\n",
            self.changesetid,
            String::from_utf8_lossy(self.user()),
            self.time().as_chrono().to_rfc2822(),
            String::from_utf8_lossy(&self.message()[0..title_end])
        )
    }
}


/*
 * 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 revlog;
pub use revlog::serialize_cs;
pub use revlog::serialize_extras;
pub use revlog::Extra;
pub use revlog::RevlogChangeset;

mod blob;
pub use blob::ChangesetMetadata;
pub use blob::HgBlobChangeset;
pub use blob::HgChangesetContent;

#[cfg(test)]
mod test;


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

use anyhow::bail;
use anyhow::Context;
use anyhow::Error;
use anyhow::Result;
use blobstore::Blobstore;
use bytes::Bytes;
use context::CoreContext;
use mononoke_types::DateTime;

use crate::HgBlob;
use crate::HgBlobNode;
use crate::HgChangesetEnvelope;
use crate::HgChangesetId;
use crate::HgManifestId;
use crate::HgNodeHash;
use crate::HgParents;
use crate::MPath;
use crate::NULL_HASH;

// The `user` and `comments` fields are expected to be utf8 encoded, but
// some older commits might be corrupted. We handle them as pure binary here
// and higher levels can convert to utf8 as needed.
// See https://www.mercurial-scm.org/wiki/EncodingStrategy for details.
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct RevlogChangeset {
    pub p1: Option<HgNodeHash>,
    pub p2: Option<HgNodeHash>,
    pub manifestid: HgManifestId,
    pub user: Vec<u8>,
    pub time: DateTime,
    pub extra: Extra,
    pub files: Vec<MPath>,
    pub message: Vec<u8>,
}

#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
pub struct Extra(pub(super) BTreeMap<Vec<u8>, Vec<u8>>);

impl AsRef<BTreeMap<Vec<u8>, Vec<u8>>> for Extra {
    fn as_ref(&self) -> &BTreeMap<Vec<u8>, Vec<u8>> {
        &self.0
    }
}

fn parseline<'a, I, F, T>(lines: &mut I, parse: F) -> Result<T>
where
    I: Iterator<Item = &'a [u8]>,
    F: Fn(&'a [u8]) -> Result<T>,
{
    match lines.next() {
        Some(s) => parse(s).map_err(Into::into),
        None => bail!("premature end"),
    }
}

pub(super) fn escape<'a, S: IntoIterator<Item = &'a u8>>(s: S) -> Vec<u8> {
    let mut ret = Vec::new();

    for c in s.into_iter() {
        match *c {
            b'\0' => ret.extend_from_slice(&b"\\0"[..]),
            b'\n' => ret.extend_from_slice(&b"\\n"[..]),
            b'\r' => ret.extend_from_slice(&b"\\r"[..]),
            b'\\' => ret.extend_from_slice(&b"\\\\"[..]),
            c => ret.push(c),
        }
    }

    ret
}

pub(super) fn unescape<'a, S: IntoIterator<Item = &'a u8>>(s: S) -> Vec<u8> {
    let mut ret = Vec::new();
    let mut quote = false;

    for c in s.into_iter() {
        match *c {
            b'0' if quote => {
                quote = false;
                ret.push(b'\0');
            }
            b'n' if quote => {
                quote = false;
                ret.push(b'\n');
            }
            b'r' if quote => {
                quote = false;
                ret.push(b'\r');
            }
            b'\\' if quote => {
                quote = false;
                ret.push(b'\\');
            }
            c if quote => {
                quote = false;
                ret.push(b'\\');
                ret.push(c)
            }
            b'\\' => {
                assert!(!quote);
                quote = true;
            }
            c => {
                quote = false;
                ret.push(c);
            }
        }
    }

    ret
}

impl Extra {
    pub fn new(extra: BTreeMap<Vec<u8>, Vec<u8>>) -> Self {
        Extra(extra)
    }

    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    pub(super) fn from_slice<S: AsRef<[u8]>>(s: Option<S>) -> Result<Extra> {
        let mut ret = BTreeMap::new();

        if let Some(s) = s {
            let s = s.as_ref();

            for kv in s.split(|c| *c == b'\0') {
                let kv: Vec<_> = kv.splitn(2, |c| *c == b':').collect();
                if kv.len() == 2 {
                    ret.insert(unescape(kv[0]), unescape(kv[1]));
                }
            }
        }

        Ok(Extra(ret))
    }
}

fn try_get<T>(v: &[T], idx: usize) -> Option<&T> {
    if idx < v.len() { Some(&v[idx]) } else { None }
}

// Time has the format: time tz extra\n
// "date (time is int or float, timezone is int)"
//     - in what units? time is seconds from epoch?
//     - what's TZ? seconds offset from UTC?
//
// Extra is key:value, \0 separated, with \\, \0, \n escaped
fn parsetimeextra<S: AsRef<[u8]>>(s: S) -> Result<(DateTime, Extra)> {
    let s = s.as_ref();
    let parts: Vec<_> = s.splitn(3, |c| *c == b' ').collect();

    if parts.len() < 2 {
        bail!("not enough parts");
    }
    let time: i64 = str::from_utf8(parts[0])?
        .parse::<i64>()
        .context("can't parse time")?;
    let tz: i32 = str::from_utf8(parts[1])?
        .parse::<i32>()
        .context("can't parse tz")?;

    let extras = Extra::from_slice(try_get(parts.as_ref(), 2))?;

    Ok((DateTime::from_timestamp(time, tz)?, extras))
}

impl RevlogChangeset {
    pub fn new_from_parts(
        // XXX replace parents with p1 and p2
        parents: HgParents,
        manifestid: HgManifestId,
        user: Vec<u8>,
        time: DateTime,
        extra: BTreeMap<Vec<u8>, Vec<u8>>,
        files: Vec<MPath>,
        message: Vec<u8>,
    ) -> Self {
        let (p1, p2) = parents.get_nodes();
        Self {
            p1,
            p2,
            manifestid,
            user,
            time,
            extra: Extra(extra),
            files,
            message,
        }
    }

    pub fn new(node: HgBlobNode) -> Result<Self> {
        let (p1, p2) = node.parents().get_nodes();
        Self::parse(node.as_blob().clone(), p1, p2)
    }

    pub fn from_envelope(envelope: HgChangesetEnvelope) -> Result<Self> {
        let envelope = envelope.into_mut();
        Self::parse(
            envelope.contents.into(),
            envelope.p1.map(HgChangesetId::into_nodehash),
            envelope.p2.map(HgChangesetId::into_nodehash),
        )
    }

    pub async fn load<'a, B: Blobstore>(
        ctx: &'a CoreContext,
        blobstore: &'a B,
        changesetid: HgChangesetId,
    ) -> Result<Option<RevlogChangeset>> {
        if changesetid == HgChangesetId::new(NULL_HASH) {
            let revlogcs = RevlogChangeset::new_null();
            Ok(Some(revlogcs))
        } else {
            let key = changesetid.blobstore_key();

            async {
                let got = blobstore.get(ctx, &key).await?;
                match got {
                    None => Ok(None),
                    Some(bytes) => {
                        let envelope = HgChangesetEnvelope::from_blob(bytes.into())?;
                        if changesetid != envelope.node_id() {
                            bail!(
                                "Changeset ID mismatch (requested: {}, got: {})",
                                changesetid,
                                envelope.node_id()
                            );
                        }
                        let revlogcs = RevlogChangeset::from_envelope(envelope)?;
                        Ok(Some(revlogcs))
                    }
                }
            }
            .await
            .with_context(move || {
                format!(
                    "Error while deserializing changeset retrieved from key '{}'",
                    key
                )
            })
        }
    }

    pub fn new_null() -> Self {
        Self {
            p1: None,
            p2: None,
            manifestid: HgManifestId::new(NULL_HASH),
            user: Vec::new(),
            time: DateTime::from_timestamp(0, 0).expect("this is a valid DateTime"),
            extra: Extra(BTreeMap::new()),
            files: Vec::new(),
            message: Vec::new(),
        }
    }

    // format used:
    // nodeid\n        : manifest node in ascii
    // user\n          : user, no \n or \r allowed
    // time tz extra\n : date (time is int or float, timezone is int)
    //                 : extra is metadata, encoded and separated by '\0'
    //                 : older versions ignore it
    // files\n\n       : files modified by the cset, no \n or \r allowed
    // (.*)            : comment (free text, ideally utf-8)
    //
    // changelog v0 doesn't use extra
    //
    // XXX Any constraints on/syntax of "user"?
    // XXX time units? tz meaning?
    // XXX Files sorted? No escaping?
    // XXX "extra" - how sorted? What encoding?
    // XXX "comment" - line endings normalized at all?
    pub(super) fn parse(
        blob: HgBlob,
        p1: Option<HgNodeHash>,
        p2: Option<HgNodeHash>,
    ) -> Result<Self> {
        // This is awkward - we want to store the node in the resulting
        // RevlogChangeset but we need to borrow from it to parse its data. Set up a
        // partially initialized RevlogChangeset then fill it in as we go.
        let mut ret = Self {
            p1,
            p2,
            manifestid: HgManifestId::new(NULL_HASH),
            user: Vec::new(),
            time: DateTime::from_timestamp(0, 0).expect("this is a valid DateTime"),
            extra: Extra(BTreeMap::new()),
            files: Vec::new(),
            message: Vec::new(),
        };

        {
            let data = blob.as_slice();
            let mut lines = data.split(|b| *b == b'\n');

            let nodehash = parseline(&mut lines, |l| HgNodeHash::from_str(str::from_utf8(l)?))
                .context("can't get hash")?;
            ret.manifestid = HgManifestId::new(nodehash);
            ret.user =
                parseline(&mut lines, |u| Ok::<_, Error>(u.to_vec())).context("can't get user")?;
            let (time, extra) =
                parseline(&mut lines, parsetimeextra).context("can't get time/extra")?;

            ret.time = time;
            ret.extra = extra;

            let mut files = Vec::new();
            let mut message = Vec::new();

            // List of files followed by the message. The file list is one entry
            // per line, with a blank line delimiting the end. The message is a single
            // binary blob with no internal structure, but we've already split it on '\n'
            // bounaries, so we can glue it back together to re-create the original content.
            //
            // XXX: We assume the comment is utf-8. Is this a good assumption?
            let mut dofiles = true;
            for line in lines {
                if dofiles {
                    if line.is_empty() {
                        dofiles = false;
                        continue;
                    }
                    files.push(MPath::new(line).context("invalid path in changelog")?)
                } else {
                    message.push(line);
                }
            }

            ret.files = files;
            ret.message = message.join(&b'\n');
        }

        Ok(ret)
    }

    /// Generate a serialized changeset. This is the counterpart to parse, and generates
    /// in the same format as Mercurial. It should be bit-for-bit identical in fact.
    pub fn generate<W: Write>(&self, out: &mut W) -> Result<()> {
        serialize_cs(self, out)
    }

    /// Generate bytes where SHA1(bytes) is expected to match `get_node().nodeid()`.
    ///
    /// By Mercurial definition, `bytes` is `min(p1, p2) + max(p1, p2) + data`.
    ///
    /// This includes the `data` that `generate()` produces, with the extra
    /// header so it's possible to check data integrity against commit hashes.
    ///
    /// Do NOT use the first 40 bytes for "parents" information! Parent order
    /// should always be preserved and this function does not preserve the order.
    pub fn generate_for_hash_verification<W: Write>(&self, out: &mut W) -> Result<()> {
        serialize_sorted_parents(self, out)?;
        self.generate(out)
    }

    pub fn get_node(&self) -> Result<HgBlobNode> {
        let mut v = Vec::new();
        self.generate(&mut v)?;
        Ok(HgBlobNode::new(Bytes::from(v), self.p1(), self.p2()))
    }

    pub fn manifestid(&self) -> HgManifestId {
        self.manifestid
    }

    pub fn user(&self) -> &[u8] {
        &self.user
    }

    pub fn extra(&self) -> &BTreeMap<Vec<u8>, Vec<u8>> {
        &self.extra.0
    }

    pub fn message(&self) -> &[u8] {
        self.message.as_ref()
    }

    pub fn files(&self) -> &[MPath] {
        self.files.as_ref()
    }

    pub fn time(&self) -> &DateTime {
        &self.time
    }

    #[inline]
    pub fn parents(&self) -> HgParents {
        HgParents::new(self.p1(), self.p2())
    }

    #[inline]
    pub fn p1(&self) -> Option<HgNodeHash> {
        self.p1
    }

    #[inline]
    pub fn p2(&self) -> Option<HgNodeHash> {
        self.p2
    }
}

/// Generate a serialized changeset. This is the counterpart to parse, and generates
/// in the same format as Mercurial. It should be bit-for-bit identical in fact.
pub fn serialize_cs<W: Write>(cs: &RevlogChangeset, out: &mut W) -> Result<()> {
    write!(out, "{}\n", cs.manifestid().into_nodehash())?;
    out.write_all(cs.user())?;
    out.write_all(b"\n")?;
    write!(
        out,
        "{} {}",
        cs.time().timestamp_secs(),
        cs.time().tz_offset_secs()
    )?;

    if !cs.extra().is_empty() {
        write!(out, " ")?;
        serialize_extras(&cs.extra, out)?;
    }

    write!(out, "\n")?;
    for f in cs.files() {
        write!(out, "{}\n", f)?;
    }
    write!(out, "\n")?;
    out.write_all(cs.message())?;

    Ok(())
}

fn serialize_sorted_parents<W: Write>(cs: &RevlogChangeset, out: &mut W) -> Result<()> {
    let p1 = cs.p1().unwrap_or(NULL_HASH);
    let p2 = cs.p2().unwrap_or(NULL_HASH);
    let p1: &[u8] = p1.as_bytes();
    let p2: &[u8] = p2.as_bytes();
    out.write_all(p1.min(p2))?;
    out.write_all(p1.max(p2))?;
    Ok(())
}

pub fn serialize_extras<W: Write>(extras: &Extra, out: &mut W) -> io::Result<()> {
    // assume BTreeMap is sorted enough
    let kv: Vec<_> = extras
        .0
        .iter()
        .map(|(k, v)| {
            let mut vec = Vec::new();
            vec.extend_from_slice(k);
            vec.push(b':');
            vec.extend_from_slice(v);
            escape(&vec)
        })
        .collect();
    out.write_all(kv.join(&b'\0').as_slice())
}

#[cfg(test)]
mod tests {
    use mononoke_types::sha1_hash;
    use quickcheck::quickcheck;

    use super::*;

    quickcheck! {
        fn escape_roundtrip(input: Vec<u8>) -> bool {
            let result = escape(input.iter());
            unescape(result.iter()) == input
        }
    }

    #[test]
    fn unescape_example_roundtrip() {
        let input = b"\x0c\\r\x90\x0c\x01\\n";
        let result = unescape(input.iter());
        assert_eq!(escape(result.iter()), input);
    }

    #[test]
    fn sha1_bytes_match_hashes() {
        // Commit raw data created by: `hg init`, `hg commit -m y --config ui.allowemptycommit=1`,
        // then "[cl.revision(i) for i in cl]" in `hg dbsh`.
        let raw_texts = [
            b"0000000000000000000000000000000000000000\nJun Wu <quark@fb.com>\n1586367898 25200\n\nx",
            b"0000000000000000000000000000000000000000\nJun Wu <quark@fb.com>\n1586367993 25200\n\ny",
            b"0000000000000000000000000000000000000000\nJun Wu <quark@fb.com>\n1586375872 25200\n\nz",
            b"0000000000000000000000000000000000000000\nJun Wu <quark@fb.com>\n1586375896 25200\n\nm",
        ];

        // Commit hashes generated by Mercurial `[cl.node(i) for i in cl]` in `hg dbsh`.
        let hashes = [
            make_hash(b"6u'\\!,\xcf\xf7\xa2\xc8\x96\xf2\xf0\xc5\xa6\xa3\xfa\x1ay\xd8"),
            make_hash(b"\xc5(\xa0\xd0\xef\xce\xa2\xd9yI\x95\x87\xda\xa1\xeb\xa1\xdc\xb0>\xe6"),
            make_hash(b"s\xd2='\x11\xe1\xbdT\xdbgm\r\x89\x06\x95\x97\xab\xba\x0c\x8c"),
            make_hash(b"\xd8\xc7f/a2:\xa7l\xef\xfe+}4U\xb9\xcc_\x88\xab"),
        ];

        // Prepare linear changesets.
        let changesets = [
            make_changeset(raw_texts[0], None, None),
            make_changeset(raw_texts[1], Some(hashes[0]), None),
            make_changeset(raw_texts[2], None, None),
            make_changeset(raw_texts[3], Some(hashes[2]), Some(hashes[1])),
        ];

        // Commit hashes by the get_node().nodeid() API.
        let hashes_via_get_node: Vec<HgNodeHash> = changesets
            .iter()
            .map(|c| c.get_node().unwrap().nodeid())
            .collect();

        // Commit hashes by the sha1(generate_for_hash_verification()) API.
        let hashes_via_sha1_bytes: Vec<HgNodeHash> = changesets
            .iter()
            .map(|c| {
                let mut buf = Vec::new();
                c.generate_for_hash_verification(&mut buf).unwrap();
                let mut hash_context = sha1_hash::Context::new();
                hash_context.update(buf);
                let sha1 = hash_context.finish();
                HgNodeHash::new(sha1)
            })
            .collect();

        assert_eq!(&hashes[..], &hashes_via_get_node[..]);
        assert_eq!(&hashes[..], &hashes_via_sha1_bytes[..]);
    }

    fn make_hash(hash: &[u8]) -> HgNodeHash {
        HgNodeHash::from_bytes(hash).unwrap()
    }

    fn make_changeset(
        text: &'static [u8],
        p1: Option<HgNodeHash>,
        p2: Option<HgNodeHash>,
    ) -> RevlogChangeset {
        RevlogChangeset::parse(HgBlob::new(Bytes::from_static(text)), p1, p2).unwrap()
    }
}


/*
 * 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 bytes::Bytes;
use mononoke_types::DateTime;
use quickcheck::quickcheck;
use quickcheck::QuickCheck;
use quickcheck::TestResult;

use super::revlog::escape;
use super::revlog::serialize_extras;
use super::revlog::unescape;
use super::revlog::Extra;
use super::revlog::RevlogChangeset;
use crate::HgBlob;
use crate::HgBlobNode;
use crate::HgManifestId;
use crate::HgNodeHash;
use crate::MPath;

const CHANGESET: &[u8] = include_bytes!("cset.bin");
const CHANGESET_NOEXTRA: &[u8] = include_bytes!("cset_noextra.bin");

#[test]
fn test_parse() {
    let csid: HgNodeHash = "0849d280663e46b3e247857f4a68fabd2ba503c3".parse().unwrap();
    let p1: HgNodeHash = "169cb9e47f8e86079ee9fd79972092f78fbf68b1".parse().unwrap();
    let cset = RevlogChangeset::parse(HgBlob::new(Bytes::from(CHANGESET)), Some(p1), None)
        .expect("parsed");

    let node = HgBlobNode::new(HgBlob::new(Bytes::from(CHANGESET)), Some(p1), None);
    assert_eq!(node.nodeid(), csid);

    assert_eq!(
        cset,
        RevlogChangeset {
            p1: Some(p1),
            p2: None,
            manifestid: HgManifestId::new(
                "497522ef3706a1665bf4140497c65b467454e962".parse().unwrap()
            ),
            user: "Mads Kiilerich <madski@unity3d.com>".into(),
            time: DateTime::from_timestamp(1383910550, -3600).expect("valid timestamp"),
            extra: Extra(
                vec![("branch".into(), "stable".into())]
                    .into_iter()
                    .collect()
            ),
            files: vec![MPath::new(b"mercurial/util.py").unwrap()],
            message: r#"util: warn when adding paths ending with \

Paths ending with \ will fail the verification introduced in 684a977c2ae0 when
checking out on Windows ... and if it didn't fail it would probably not do what
the user expected."#
                .into(),
        }
    );

    let csid: HgNodeHash = "526722d24ee5b3b860d4060e008219e083488356".parse().unwrap();
    let p1: HgNodeHash = "db5eb6a86179ce819db03da9ef2090b32f8e3fc4".parse().unwrap();
    let cset = RevlogChangeset::parse(HgBlob::new(Bytes::from(CHANGESET_NOEXTRA)), Some(p1), None)
        .expect("parsed");

    let node = HgBlobNode::new(Bytes::from(CHANGESET_NOEXTRA), Some(p1), None);
    assert_eq!(node.nodeid(), csid);

    assert_eq!(
        cset,
        RevlogChangeset {
            p1: Some(p1),
            p2: None,
            manifestid: HgManifestId::new(
                "6c0d10b92d045127f9a3846b59480451fe3bbac9".parse().unwrap()
            ),
            user: "jake@edge2.net".into(),
            time: DateTime::from_timestamp(1116031690, 25200).expect("valid timestamp"),
            extra: Extra(vec![].into_iter().collect()),
            files: vec![MPath::new(b"hgweb.py").unwrap()],
            message: r#"reorganize code into classes
clean up html code for w3c validation
"#
            .into(),
        }
    );
}

#[test]
fn test_generate() {
    fn test(csid: HgNodeHash, p1: Option<HgNodeHash>, blob: HgBlob, cs: &[u8]) {
        let cset = RevlogChangeset::parse(blob.clone(), p1, None).expect("parsed");

        let node = HgBlobNode::new(blob, p1, None);
        assert_eq!(node.nodeid(), csid);

        let mut new = Vec::new();

        cset.generate(&mut new).expect("generate failed");

        assert_eq!(new, cs);
    }

    let csid: HgNodeHash = "0849d280663e46b3e247857f4a68fabd2ba503c3".parse().unwrap();
    let p1: HgNodeHash = "169cb9e47f8e86079ee9fd79972092f78fbf68b1".parse().unwrap();
    test(
        csid,
        Some(p1),
        HgBlob::new(Bytes::from(CHANGESET)),
        CHANGESET,
    );

    let csid: HgNodeHash = "526722d24ee5b3b860d4060e008219e083488356".parse().unwrap();
    let p1: HgNodeHash = "db5eb6a86179ce819db03da9ef2090b32f8e3fc4".parse().unwrap();
    test(
        csid,
        Some(p1),
        HgBlob::new(Bytes::from(CHANGESET_NOEXTRA)),
        CHANGESET_NOEXTRA,
    );
}

quickcheck! {
    fn escape_roundtrip(s: Vec<u8>) -> bool {
        let esc = escape(&s);
        let unesc = unescape(&esc);
        if s != unesc {
            println!("s: {:?}, esc: {:?}, unesc: {:?}", s, esc, unesc)
        }
        s == unesc
    }
}

fn extras_roundtrip_prop(kv: BTreeMap<Vec<u8>, Vec<u8>>) -> TestResult {
    if kv.keys().any(|k| k.contains(&b':')) {
        return TestResult::discard();
    }

    let extra = Extra(kv);
    let mut enc = Vec::new();
    let () = serialize_extras(&extra, &mut enc).expect("enc failed");
    let new = Extra::from_slice(Some(&enc)).expect("parse failed");

    TestResult::from_bool(new == extra)
}

#[test]
fn extras_roundtrip() {
    QuickCheck::new()
        .tests(50) // more takes too much time
        .quickcheck(extras_roundtrip_prop as fn(BTreeMap<Vec<u8>, Vec<u8>>) -> TestResult);
}


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

//! Envelopes used for Changeset nodes.

use std::fmt;

use anyhow::Context;
use anyhow::Error;
use anyhow::Result;
use bytes::Bytes;
use fbthrift::compact_protocol;
use quickcheck::empty_shrinker;
use quickcheck::Arbitrary;
use quickcheck::Gen;

use super::HgEnvelopeBlob;
use crate::errors::*;
use crate::nodehash::HgChangesetId;
use crate::thrift;

/// A mutable representation of a Mercurial changeset node.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct HgChangesetEnvelopeMut {
    pub node_id: HgChangesetId,
    pub p1: Option<HgChangesetId>,
    pub p2: Option<HgChangesetId>,
    pub contents: Bytes,
}

impl HgChangesetEnvelopeMut {
    pub fn freeze(self) -> HgChangesetEnvelope {
        HgChangesetEnvelope { inner: self }
    }
}

impl fmt::Display for HgChangesetEnvelopeMut {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        writeln!(f, "node id: {}", self.node_id)?;
        writeln!(f, "p1: {}", HgChangesetId::display_opt(self.p1.as_ref()))?;
        writeln!(f, "p2: {}", HgChangesetId::display_opt(self.p2.as_ref()))?;
        // TODO: (rain1) T30970792 parse contents and print out in a better fashion
        writeln!(f, "contents: {:?}", self.contents)
    }
}

/// A serialized representation of a Mercurial Changeset node in the blob store.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct HgChangesetEnvelope {
    inner: HgChangesetEnvelopeMut,
}

impl HgChangesetEnvelope {
    pub(crate) fn from_thrift(fe: thrift::HgChangesetEnvelope) -> Result<Self> {
        let catch_block = || -> Result<_> {
            Ok(Self {
                inner: HgChangesetEnvelopeMut {
                    node_id: HgChangesetId::from_thrift(fe.node_id)?,
                    p1: HgChangesetId::from_thrift_opt(fe.p1)?,
                    p2: HgChangesetId::from_thrift_opt(fe.p2)?,
                    contents: Bytes::from(
                        fe.contents
                            .ok_or_else(|| Error::msg("missing contents field"))?,
                    ),
                },
            })
        };

        catch_block().with_context(|| {
            ErrorKind::InvalidThrift(
                "HgChangesetEnvelope".into(),
                "Invalid Changeset envelope".into(),
            )
        })
    }

    pub fn from_blob(blob: HgEnvelopeBlob) -> Result<Self> {
        let thrift_tc = compact_protocol::deserialize(blob.0.as_ref())
            .with_context(|| ErrorKind::BlobDeserializeError("HgChangesetEnvelope".into()))?;
        Self::from_thrift(thrift_tc)
    }

    /// The ID for this changeset, as recorded by Mercurial. This is expected to match the
    /// actual hash computed from the contents.
    #[inline]
    pub fn node_id(&self) -> HgChangesetId {
        self.inner.node_id
    }

    /// The parent hashes for this node. The order matters.
    #[inline]
    pub fn parents(&self) -> (Option<HgChangesetId>, Option<HgChangesetId>) {
        (self.inner.p1, self.inner.p2)
    }

    /// The changeset contents as raw bytes.
    #[inline]
    pub fn contents(&self) -> &Bytes {
        &self.inner.contents
    }

    /// Convert into a mutable representation.
    #[inline]
    pub fn into_mut(self) -> HgChangesetEnvelopeMut {
        self.inner
    }

    pub(crate) fn into_thrift(self) -> thrift::HgChangesetEnvelope {
        let inner = self.inner;
        thrift::HgChangesetEnvelope {
            node_id: inner.node_id.into_thrift(),
            p1: inner.p1.map(HgChangesetId::into_thrift),
            p2: inner.p2.map(HgChangesetId::into_thrift),
            contents: Some(inner.contents.to_vec()),
        }
    }

    /// Serialize this structure into a blob.
    #[inline]
    pub fn into_blob(self) -> HgEnvelopeBlob {
        let thrift = self.into_thrift();
        HgEnvelopeBlob(compact_protocol::serialize(&thrift))
    }
}

impl fmt::Display for HgChangesetEnvelope {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.inner.fmt(f)
    }
}

impl Arbitrary for HgChangesetEnvelope {
    fn arbitrary(g: &mut Gen) -> Self {
        HgChangesetEnvelope {
            inner: HgChangesetEnvelopeMut {
                // XXX this doesn't ensure that the node ID actually matches the contents.
                // Might want to do that.
                node_id: Arbitrary::arbitrary(g),
                p1: Arbitrary::arbitrary(g),
                p2: Arbitrary::arbitrary(g),
                contents: Bytes::from(Vec::arbitrary(g)),
            },
        }
    }

    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
        empty_shrinker()
    }
}

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

    use super::*;

    quickcheck! {
        fn thrift_roundtrip(ce: HgChangesetEnvelope) -> bool {
            let thrift_ce = ce.clone().into_thrift();
            let ce2 = HgChangesetEnvelope::from_thrift(thrift_ce)
                .expect("thrift roundtrips should always be valid");
            ce == ce2
        }

        fn blob_roundtrip(ce: HgChangesetEnvelope) -> bool {
            let blob = ce.clone().into_blob();
            let ce2 = HgChangesetEnvelope::from_blob(blob)
                .expect("blob roundtrips should always be valid");
            ce == ce2
        }
    }

    #[test]
    fn bad_thrift() {
        let mut thrift_ce = thrift::HgChangesetEnvelope {
            node_id: thrift::HgNodeHash(thrift::Sha1(vec![1; 20].into())),
            p1: Some(thrift::HgNodeHash(thrift::Sha1(vec![2; 20].into()))),
            p2: None,
            // contents must be present
            contents: None,
        };

        HgChangesetEnvelope::from_thrift(thrift_ce.clone())
            .expect_err("unexpected OK -- missing contents");

        thrift_ce.contents = Some(b"abc".to_vec());
        thrift_ce.node_id = thrift::HgNodeHash(thrift::Sha1(vec![1; 19].into()));

        HgChangesetEnvelope::from_thrift(thrift_ce)
            .expect_err("unexpected OK -- wrong hash length");
    }
}


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

//! Envelopes used for file nodes.

use std::fmt;

use anyhow::Context;
use anyhow::Error;
use anyhow::Result;
use bytes::Bytes;
use fbthrift::compact_protocol;
use mononoke_types::ContentId;
use quickcheck::empty_shrinker;
use quickcheck::Arbitrary;
use quickcheck::Gen;

use super::HgEnvelopeBlob;
use crate::errors::*;
use crate::nodehash::HgFileNodeId;
use crate::nodehash::HgNodeHash;
use crate::thrift;
use crate::HgParents;

/// A mutable representation of a Mercurial file node.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct HgFileEnvelopeMut {
    pub node_id: HgFileNodeId,
    pub p1: Option<HgFileNodeId>,
    pub p2: Option<HgFileNodeId>,
    pub content_id: ContentId,
    pub content_size: u64,
    pub metadata: Bytes,
}

impl HgFileEnvelopeMut {
    pub fn freeze(self) -> HgFileEnvelope {
        HgFileEnvelope { inner: self }
    }
}

impl fmt::Display for HgFileEnvelopeMut {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        writeln!(f, "node id: {}", self.node_id)?;
        writeln!(
            f,
            "p1: {}",
            HgNodeHash::display_opt(self.p1.map(HgFileNodeId::into_nodehash).as_ref())
        )?;
        writeln!(
            f,
            "p2: {}",
            HgNodeHash::display_opt(self.p2.map(HgFileNodeId::into_nodehash).as_ref())
        )?;
        writeln!(f, "content id: {}", self.content_id)?;
        writeln!(f, "content size: {}", self.content_size)?;
        writeln!(f, "metadata: {:?}", self.metadata)
    }
}

/// A serialized representation of a Mercurial file node in the blob store.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct HgFileEnvelope {
    inner: HgFileEnvelopeMut,
}

impl HgFileEnvelope {
    pub(crate) fn from_thrift(fe: thrift::HgFileEnvelope) -> Result<Self> {
        let catch_block = || -> Result<_> {
            Ok(Self {
                inner: HgFileEnvelopeMut {
                    node_id: HgFileNodeId::new(HgNodeHash::from_thrift(fe.node_id)?),
                    p1: HgNodeHash::from_thrift_opt(fe.p1)?.map(HgFileNodeId::new),
                    p2: HgNodeHash::from_thrift_opt(fe.p2)?.map(HgFileNodeId::new),
                    content_id: ContentId::from_thrift(
                        fe.content_id
                            .ok_or_else(|| Error::msg("missing content id field"))?,
                    )?,
                    content_size: fe.content_size as u64,
                    // metadata will always be stored, even if it's length 0
                    metadata: Bytes::from(
                        fe.metadata
                            .ok_or_else(|| Error::msg("missing metadata field"))?,
                    ),
                },
            })
        };

        catch_block().with_context(|| {
            ErrorKind::InvalidThrift("HgFileEnvelope".into(), "Invalid file envelope".into())
        })
    }

    pub fn from_blob(blob: HgEnvelopeBlob) -> Result<Self> {
        let thrift_tc = compact_protocol::deserialize(blob.0.as_ref())
            .with_context(|| ErrorKind::BlobDeserializeError("HgFileEnvelope".into()))?;
        Self::from_thrift(thrift_tc)
    }

    /// The ID for this file version.
    #[inline]
    pub fn node_id(&self) -> HgFileNodeId {
        self.inner.node_id
    }

    /// The parent hashes for this node. The order matters.
    #[inline]
    pub fn parents(&self) -> (Option<HgFileNodeId>, Option<HgFileNodeId>) {
        (self.inner.p1, self.inner.p2)
    }

    #[inline]
    pub fn hg_parents(&self) -> HgParents {
        HgParents::new(
            self.inner.p1.map(|p| p.into_nodehash()),
            self.inner.p2.map(|p| p.into_nodehash()),
        )
    }

    /// The content ID -- this can be used to retrieve the contents.
    #[inline]
    pub fn content_id(&self) -> ContentId {
        self.inner.content_id
    }

    /// The size of the content ID, not counting the metadata.
    #[inline]
    pub fn content_size(&self) -> u64 {
        self.inner.content_size
    }

    /// The metadata for this node, exactly as provided to Mercurial. This is extracted from
    /// and prepended to the content for Mercurial.
    #[inline]
    pub fn metadata(&self) -> &Bytes {
        &self.inner.metadata
    }

    /// Convert into a mutable representation.
    #[inline]
    pub fn into_mut(self) -> HgFileEnvelopeMut {
        self.inner
    }

    pub(crate) fn into_thrift(self) -> thrift::HgFileEnvelope {
        let inner = self.inner;
        thrift::HgFileEnvelope {
            node_id: inner.node_id.into_nodehash().into_thrift(),
            p1: inner.p1.map(|p| p.into_nodehash().into_thrift()),
            p2: inner.p2.map(|p| p.into_nodehash().into_thrift()),
            content_id: Some(inner.content_id.into_thrift()),
            content_size: inner.content_size as i64,
            metadata: Some(inner.metadata.to_vec()),
        }
    }

    /// Serialize this structure into a blob.
    #[inline]
    pub fn into_blob(self) -> HgEnvelopeBlob {
        let thrift = self.into_thrift();
        HgEnvelopeBlob(compact_protocol::serialize(&thrift))
    }
}

impl fmt::Display for HgFileEnvelope {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.inner.fmt(f)
    }
}

impl Arbitrary for HgFileEnvelope {
    fn arbitrary(g: &mut Gen) -> Self {
        HgFileEnvelope {
            inner: HgFileEnvelopeMut {
                node_id: Arbitrary::arbitrary(g),
                p1: Arbitrary::arbitrary(g),
                p2: Arbitrary::arbitrary(g),
                content_id: Arbitrary::arbitrary(g),
                content_size: Arbitrary::arbitrary(g),
                metadata: Bytes::from(Vec::arbitrary(g)),
            },
        }
    }

    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
        empty_shrinker()
    }
}

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

    use super::*;

    quickcheck! {
        fn thrift_roundtrip(fe: HgFileEnvelope) -> bool {
            let thrift_fe = fe.clone().into_thrift();
            let fe2 = HgFileEnvelope::from_thrift(thrift_fe)
                .expect("thrift roundtrips should always be valid");
            fe == fe2
        }

        fn blob_roundtrip(fe: HgFileEnvelope) -> bool {
            let blob = fe.clone().into_blob();
            let fe2 = HgFileEnvelope::from_blob(blob)
                .expect("blob roundtrips should always be valid");
            fe == fe2
        }
    }

    #[test]
    fn bad_thrift() {
        let mut thrift_fe = thrift::HgFileEnvelope {
            node_id: thrift::HgNodeHash(thrift::Sha1(vec![1; 20].into())),
            p1: Some(thrift::HgNodeHash(thrift::Sha1(vec![2; 20].into()))),
            p2: None,
            // a content ID must be present
            content_id: None,
            content_size: 42,
            metadata: Some(vec![]),
        };

        HgFileEnvelope::from_thrift(thrift_fe.clone())
            .expect_err("unexpected OK -- missing content ID");

        thrift_fe.content_id = Some(thrift::ContentId(thrift::IdType::Blake2(thrift::Blake2(
            vec![3; 32].into(),
        ))));
        thrift_fe.metadata = None;

        HgFileEnvelope::from_thrift(thrift_fe).expect_err("unexpected OK -- missing metadata");
    }
}


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

//! Envelopes used for manifest nodes.

use std::fmt;

use anyhow::Context;
use anyhow::Error;
use anyhow::Result;
use bytes::Bytes;
use fbthrift::compact_protocol;
use quickcheck::empty_shrinker;
use quickcheck::Arbitrary;
use quickcheck::Gen;

use super::HgEnvelopeBlob;
use crate::errors::*;
use crate::nodehash::HgNodeHash;
use crate::thrift;

/// A mutable representation of a Mercurial manifest node.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct HgManifestEnvelopeMut {
    pub node_id: HgNodeHash,
    pub p1: Option<HgNodeHash>,
    pub p2: Option<HgNodeHash>,
    pub computed_node_id: HgNodeHash,
    pub contents: Bytes,
}

impl HgManifestEnvelopeMut {
    pub fn freeze(self) -> HgManifestEnvelope {
        HgManifestEnvelope { inner: self }
    }
}

impl fmt::Display for HgManifestEnvelopeMut {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        writeln!(f, "node id: {}", self.node_id)?;
        writeln!(f, "p1: {}", HgNodeHash::display_opt(self.p1.as_ref()))?;
        writeln!(f, "p2: {}", HgNodeHash::display_opt(self.p2.as_ref()))?;
        writeln!(f, "computed node id: {}", self.computed_node_id)?;
        // TODO: (rain1) T30973227 parse contents and print out in a better fashion
        writeln!(f, "contents: {:?}", self.contents)
    }
}

/// A serialized representation of a Mercurial manifest node in the blob store.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct HgManifestEnvelope {
    inner: HgManifestEnvelopeMut,
}

impl HgManifestEnvelope {
    pub(crate) fn from_thrift(fe: thrift::HgManifestEnvelope) -> Result<Self> {
        let catch_block = || -> Result<_> {
            Ok(Self {
                inner: HgManifestEnvelopeMut {
                    node_id: HgNodeHash::from_thrift(fe.node_id)?,
                    p1: HgNodeHash::from_thrift_opt(fe.p1)?,
                    p2: HgNodeHash::from_thrift_opt(fe.p2)?,
                    computed_node_id: HgNodeHash::from_thrift(fe.computed_node_id)?,
                    contents: Bytes::from(
                        fe.contents
                            .ok_or_else(|| Error::msg("missing contents field"))?,
                    ),
                },
            })
        };

        catch_block().with_context(|| {
            ErrorKind::InvalidThrift(
                "HgManifestEnvelope".into(),
                "Invalid manifest envelope".into(),
            )
        })
    }

    pub fn from_blob(blob: HgEnvelopeBlob) -> Result<Self> {
        let thrift_tc = compact_protocol::deserialize(blob.0.as_ref())
            .with_context(|| ErrorKind::BlobDeserializeError("HgManifestEnvelope".into()))?;
        Self::from_thrift(thrift_tc)
    }

    /// The ID for this manifest, as recorded by Mercurial. This might or might not match the
    /// actual hash computed from the contents.
    #[inline]
    pub fn node_id(&self) -> HgNodeHash {
        self.inner.node_id
    }

    /// The parent hashes for this node. The order matters.
    #[inline]
    pub fn parents(&self) -> (Option<HgNodeHash>, Option<HgNodeHash>) {
        (self.inner.p1, self.inner.p2)
    }

    /// The computed ID for this manifest. This is primarily for consistency checks.
    #[inline]
    pub fn computed_node_id(&self) -> HgNodeHash {
        self.inner.computed_node_id
    }

    /// The manifest contents as raw bytes.
    #[inline]
    pub fn contents(&self) -> &Bytes {
        &self.inner.contents
    }

    /// Convert into a mutable representation.
    #[inline]
    pub fn into_mut(self) -> HgManifestEnvelopeMut {
        self.inner
    }

    pub(crate) fn into_thrift(self) -> thrift::HgManifestEnvelope {
        let inner = self.inner;
        thrift::HgManifestEnvelope {
            node_id: inner.node_id.into_thrift(),
            p1: inner.p1.map(HgNodeHash::into_thrift),
            p2: inner.p2.map(HgNodeHash::into_thrift),
            computed_node_id: inner.computed_node_id.into_thrift(),
            contents: Some(inner.contents.to_vec()),
        }
    }

    /// Serialize this structure into a blob.
    #[inline]
    pub fn into_blob(self) -> HgEnvelopeBlob {
        let thrift = self.into_thrift();
        HgEnvelopeBlob(compact_protocol::serialize(&thrift))
    }
}

impl fmt::Display for HgManifestEnvelope {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.inner.fmt(f)
    }
}

impl Arbitrary for HgManifestEnvelope {
    fn arbitrary(g: &mut Gen) -> Self {
        HgManifestEnvelope {
            inner: HgManifestEnvelopeMut {
                node_id: Arbitrary::arbitrary(g),
                p1: Arbitrary::arbitrary(g),
                p2: Arbitrary::arbitrary(g),
                // XXX this doesn't ensure that the computed node ID actually matches the contents.
                // Might want to do that.
                computed_node_id: Arbitrary::arbitrary(g),
                contents: Bytes::from(Vec::arbitrary(g)),
            },
        }
    }

    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
        empty_shrinker()
    }
}

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

    use super::*;

    quickcheck! {
        fn thrift_roundtrip(me: HgManifestEnvelope) -> bool {
            let thrift_me = me.clone().into_thrift();
            let me2 = HgManifestEnvelope::from_thrift(thrift_me)
                .expect("thrift roundtrips should always be valid");
            me == me2
        }

        fn blob_roundtrip(me: HgManifestEnvelope) -> bool {
            let blob = me.clone().into_blob();
            let me2 = HgManifestEnvelope::from_blob(blob)
                .expect("blob roundtrips should always be valid");
            me == me2
        }
    }

    #[test]
    fn bad_thrift() {
        let mut thrift_me = thrift::HgManifestEnvelope {
            node_id: thrift::HgNodeHash(thrift::Sha1(vec![1; 20].into())),
            p1: Some(thrift::HgNodeHash(thrift::Sha1(vec![2; 20].into()))),
            p2: None,
            computed_node_id: thrift::HgNodeHash(thrift::Sha1(vec![1; 20].into())),
            // contents must be present
            contents: None,
        };

        HgManifestEnvelope::from_thrift(thrift_me.clone())
            .expect_err("unexpected OK -- missing contents");

        thrift_me.contents = Some(b"abc".to_vec());
        thrift_me.node_id = thrift::HgNodeHash(thrift::Sha1(vec![1; 19].into()));

        HgManifestEnvelope::from_thrift(thrift_me).expect_err("unexpected OK -- wrong hash length");
    }
}


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

// Containers for Mercurial data, stored in the blob store.

mod changeset_envelope;
mod file_envelope;
mod manifest_envelope;

use blobstore::BlobstoreGetData;
use bytes::Bytes;
use mononoke_types::BlobstoreBytes;

pub use self::changeset_envelope::HgChangesetEnvelope;
pub use self::changeset_envelope::HgChangesetEnvelopeMut;
pub use self::file_envelope::HgFileEnvelope;
pub use self::file_envelope::HgFileEnvelopeMut;
pub use self::manifest_envelope::HgManifestEnvelope;
pub use self::manifest_envelope::HgManifestEnvelopeMut;

#[derive(Clone, Debug)]
pub struct HgEnvelopeBlob(Bytes);

impl From<BlobstoreBytes> for HgEnvelopeBlob {
    #[inline]
    fn from(bytes: BlobstoreBytes) -> HgEnvelopeBlob {
        HgEnvelopeBlob(bytes.into_bytes())
    }
}

impl From<HgEnvelopeBlob> for BlobstoreBytes {
    #[inline]
    fn from(blob: HgEnvelopeBlob) -> BlobstoreBytes {
        BlobstoreBytes::from_bytes(blob.0)
    }
}

impl From<BlobstoreGetData> for HgEnvelopeBlob {
    #[inline]
    fn from(blob_val: BlobstoreGetData) -> HgEnvelopeBlob {
        HgEnvelopeBlob(blob_val.into_raw_bytes())
    }
}

impl From<HgEnvelopeBlob> for BlobstoreGetData {
    #[inline]
    fn from(blob: HgEnvelopeBlob) -> BlobstoreGetData {
        BlobstoreBytes::from(blob).into()
    }
}


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

#![feature(never_type)]

use std::collections::HashMap;
use std::collections::HashSet;
use std::str::FromStr;

use anyhow::Error;
use bytes::Bytes;
use bytes::BytesMut;
use mercurial_types::blobs::filenode_lookup::FileNodeIdPointer;
use mercurial_types::blobs::File;
use mercurial_types::blobs::LFSContent;
use mercurial_types::blobs::META_MARKER;
use mercurial_types::blobs::META_SZ;
use mercurial_types::nodehash::HgChangesetId;
use mercurial_types::nodehash::HgNodeHash;
use mercurial_types::FileBytes;
use mercurial_types::HgFileNodeId;
use mercurial_types::MPath;
use mercurial_types::RepoPath;
use mercurial_types::NULL_HASH;
use mercurial_types_mocks::nodehash;
use mercurial_types_mocks::nodehash::FOURS_FNID;
use mercurial_types_mocks::nodehash::ONES_FNID;
use mercurial_types_mocks::nodehash::THREES_FNID;
use mercurial_types_mocks::nodehash::TWOS_FNID;
use mononoke_types::hash::Sha256;
use mononoke_types_mocks::contentid::ONES_CTID;
use mononoke_types_mocks::contentid::TWOS_CTID;
use quickcheck::quickcheck;

#[test]
fn nodehash_option() {
    assert_eq!(NULL_HASH.into_option(), None);
    assert_eq!(HgNodeHash::from(None), NULL_HASH);

    assert_eq!(nodehash::ONES_HASH.into_option(), Some(nodehash::ONES_HASH));
    assert_eq!(
        HgNodeHash::from(Some(nodehash::ONES_HASH)),
        nodehash::ONES_HASH
    );
}

#[test]
fn nodehash_display_opt() {
    assert_eq!(
        format!("{}", HgNodeHash::display_opt(Some(&nodehash::ONES_HASH))),
        "1111111111111111111111111111111111111111"
    );
    assert_eq!(format!("{}", HgNodeHash::display_opt(None)), "(none)");
}

#[test]
fn changeset_id_display_opt() {
    assert_eq!(
        format!("{}", HgChangesetId::display_opt(Some(&nodehash::ONES_CSID))),
        "1111111111111111111111111111111111111111"
    );
    assert_eq!(format!("{}", HgChangesetId::display_opt(None)), "(none)");
}

#[test]
fn extract_meta_sz() {
    assert_eq!(META_SZ, META_MARKER.len())
}

#[test]
fn extract_meta_0() {
    const DATA: &[u8] = b"foo - no meta";

    assert_eq!(File::extract_meta(DATA), (&[][..], 0));
}

#[test]
fn extract_meta_1() {
    const DATA: &[u8] = b"\x01\n\x01\nfoo - empty meta";

    assert_eq!(File::extract_meta(DATA), (&[][..], 4));
}

#[test]
fn extract_meta_2() {
    const DATA: &[u8] = b"\x01\nabc\x01\nfoo - some meta";

    assert_eq!(File::extract_meta(DATA), (&b"abc"[..], 7));
}

#[test]
fn extract_meta_3() {
    const DATA: &[u8] = b"\x01\nfoo - bad unterminated meta";

    assert_eq!(File::extract_meta(DATA), (&[][..], 2));
}

#[test]
fn extract_meta_4() {
    const DATA: &[u8] = b"\x01\n\x01\n\x01\nfoo - bad unterminated meta";

    assert_eq!(File::extract_meta(DATA), (&[][..], 4));
}

#[test]
fn extract_meta_5() {
    const DATA: &[u8] = b"\x01\n\x01\n";

    assert_eq!(File::extract_meta(DATA), (&[][..], 4));
}

#[test]
fn parse_meta_0() {
    const DATA: &[u8] = b"foo - no meta";

    assert!(File::parse_meta(DATA).is_empty())
}

#[test]
fn test_meta_1() {
    const DATA: &[u8] = b"\x01\n\x01\nfoo - empty meta";

    assert!(File::parse_meta(DATA).is_empty())
}

#[test]
fn test_meta_2() {
    const DATA: &[u8] = b"\x01\nfoo: bar\x01\nfoo - empty meta";

    let kv: Vec<_> = File::parse_meta(DATA).into_iter().collect();

    assert_eq!(kv, vec![(b"foo".as_ref(), b"bar".as_ref())])
}

#[test]
fn test_meta_3() {
    const DATA: &[u8] = b"\x01\nfoo: bar\nblim: blop: blap\x01\nfoo - empty meta";

    let mut kv: Vec<_> = File::parse_meta(DATA).into_iter().collect();
    kv.as_mut_slice().sort();

    assert_eq!(
        kv,
        vec![
            (b"blim".as_ref(), b"blop: blap".as_ref()),
            (b"foo".as_ref(), b"bar".as_ref()),
        ]
    )
}

#[test]
fn test_hash_meta_delimiter_only_0() {
    const DELIMITER: &[u8] = b"DELIMITER";
    const DATA: &[u8] = b"DELIMITER\n";

    let mut kv: Vec<_> = File::parse_to_hash_map(DATA, DELIMITER)
        .into_iter()
        .collect();
    kv.as_mut_slice().sort();
    assert_eq!(kv, vec![(b"".as_ref(), b"".as_ref())])
}

#[test]
fn test_hash_meta_delimiter_only_1() {
    const DELIMITER: &[u8] = b"DELIMITER";
    const DATA: &[u8] = b"DELIMITER";

    let mut kv: Vec<_> = File::parse_to_hash_map(DATA, DELIMITER)
        .into_iter()
        .collect();
    kv.as_mut_slice().sort();
    assert_eq!(kv, vec![(b"".as_ref(), b"".as_ref())])
}

#[test]
fn test_hash_meta_delimiter_short_0() {
    const DELIMITER: &[u8] = b"DELIMITER";
    const DATA: &[u8] = b"DELIM";

    let mut kv: Vec<_> = File::parse_to_hash_map(DATA, DELIMITER)
        .into_iter()
        .collect();
    assert!(kv.as_mut_slice().is_empty())
}

#[test]
fn test_hash_meta_delimiter_short_1() {
    const DELIMITER: &[u8] = b"DELIMITER";
    const DATA: &[u8] = b"\n";

    let mut kv: Vec<_> = File::parse_to_hash_map(DATA, DELIMITER)
        .into_iter()
        .collect();
    assert!(kv.as_mut_slice().is_empty())
}

#[test]
fn test_parse_to_hash_map_long_delimiter() {
    const DATA: &[u8] = b"x\nfooDELIMITERbar\nfoo1DELIMITERbar1";
    const DELIMITER: &[u8] = b"DELIMITER";
    let mut kv: Vec<_> = File::parse_to_hash_map(DATA, DELIMITER)
        .into_iter()
        .collect();
    kv.as_mut_slice().sort();
    assert_eq!(
        kv,
        vec![
            (b"foo".as_ref(), b"bar".as_ref()),
            (b"foo1".as_ref(), b"bar1".as_ref()),
        ]
    )
}

fn check_meta_roundtrip(file: &[u8], meta: &[u8]) {
    let mut buff = BytesMut::new();
    buff.extend_from_slice(meta);
    buff.extend_from_slice(file);
    let f = File::new(buff.freeze(), None, None);
    assert_eq!(f.content(), file);
    assert_eq!(f.metadata(), meta);
}

#[test]
fn generate_metadata_0() {
    const FILE_BYTES: &[u8] = b"foobar";
    let file_bytes = FileBytes(Bytes::from(FILE_BYTES));
    let mut meta: Vec<u8> = vec![];
    File::generate_metadata(None, &file_bytes, &mut meta).expect("Vec::write_all should succeed");
    assert_eq!(meta.as_slice(), &b""[..]);
    check_meta_roundtrip(FILE_BYTES, &meta);

    let mut meta: Vec<u8> = vec![];
    File::generate_metadata(
        Some(&(MPath::new("foo").unwrap(), nodehash::ONES_FNID)),
        &file_bytes,
        &mut meta,
    )
    .expect("Vec::write_all should succeed");
    assert_eq!(
        meta.as_slice(),
        &b"\x01\ncopy: foo\ncopyrev: 1111111111111111111111111111111111111111\n\x01\n"[..]
    );
    check_meta_roundtrip(FILE_BYTES, &meta);
}

#[test]
fn generate_metadata_1() {
    // The meta marker in the beginning should cause metadata to unconditionally be emitted.
    const FILE_BYTES: &[u8] = b"\x01\nfoobar";
    let file_bytes = FileBytes(Bytes::from(FILE_BYTES));
    let mut meta: Vec<u8> = vec![];
    File::generate_metadata(None, &file_bytes, &mut meta).expect("Vec::write_all should succeed");
    assert_eq!(meta.as_slice(), &b"\x01\n\x01\n"[..]);
    check_meta_roundtrip(FILE_BYTES, &meta);

    let mut meta: Vec<u8> = vec![];
    File::generate_metadata(
        Some(&(MPath::new("foo").unwrap(), nodehash::ONES_FNID)),
        &file_bytes,
        &mut meta,
    )
    .expect("Vec::write_all should succeed");
    assert_eq!(
        meta.as_slice(),
        &b"\x01\ncopy: foo\ncopyrev: 1111111111111111111111111111111111111111\n\x01\n"[..]
    );
    check_meta_roundtrip(FILE_BYTES, &meta);
}

#[test]
fn test_get_lfs_hash_map() {
    const DATA: &[u8] = b"version https://git-lfs.github.com/spec/v1\noid sha256:27c0a92fc51290e3227bea4dd9e780c5035f017de8d5ddfa35b269ed82226d97\nsize 17";

    let mut kv: Vec<_> = File::parse_content_to_lfs_hash_map(DATA)
        .into_iter()
        .collect();
    kv.as_mut_slice().sort();

    assert_eq!(
        kv,
        vec![
            (
                b"oid".as_ref(),
                b"sha256:27c0a92fc51290e3227bea4dd9e780c5035f017de8d5ddfa35b269ed82226d97".as_ref(),
            ),
            (b"size".as_ref(), b"17".as_ref()),
            (
                b"version".as_ref(),
                b"https://git-lfs.github.com/spec/v1".as_ref(),
            ),
        ]
    )
}

#[test]
fn test_get_lfs_struct_0() {
    let mut kv = HashMap::new();
    kv.insert(
        b"version".as_ref(),
        b"https://git-lfs.github.com/spec/v1".as_ref(),
    );
    kv.insert(
        b"oid".as_ref(),
        b"sha256:27c0a92fc51290e3227bea4dd9e780c5035f017de8d5ddfa35b269ed82226d97".as_ref(),
    );
    kv.insert(b"size".as_ref(), b"17".as_ref());
    let lfs = File::get_lfs_struct(&kv);

    assert_eq!(
        lfs.unwrap(),
        LFSContent::new(
            "https://git-lfs.github.com/spec/v1".to_string(),
            Sha256::from_str("27c0a92fc51290e3227bea4dd9e780c5035f017de8d5ddfa35b269ed82226d97")
                .unwrap(),
            17,
            None,
        )
    )
}

#[test]
fn test_get_lfs_struct_wrong_small_sha256() {
    let mut kv = HashMap::new();
    kv.insert(
        b"version".as_ref(),
        b"https://git-lfs.github.com/spec/v1".as_ref(),
    );
    kv.insert(b"oid".as_ref(), b"sha256:123".as_ref());
    kv.insert(b"size".as_ref(), b"17".as_ref());
    let lfs = File::get_lfs_struct(&kv);

    assert!(lfs.is_err())
}

#[test]
fn test_get_lfs_struct_wrong_size() {
    let mut kv = HashMap::new();
    kv.insert(
        b"version".as_ref(),
        b"https://git-lfs.github.com/spec/v1".as_ref(),
    );
    kv.insert(
        b"oid".as_ref(),
        b"sha256:27c0a92fc51290e3227bea4dd9e780c5035f017de8d5ddfa35b269ed82226d97".as_ref(),
    );
    kv.insert(b"size".as_ref(), b"wrong_size_length".as_ref());
    let lfs = File::get_lfs_struct(&kv);

    assert!(lfs.is_err())
}

#[test]
fn test_get_lfs_struct_non_all_mandatory_fields() {
    let mut kv = HashMap::new();
    kv.insert(
        b"oid".as_ref(),
        b"sha256:27c0a92fc51290e3227bea4dd9e780c5035f017de8d5ddfa35b269ed82226d97".as_ref(),
    );
    let lfs = File::get_lfs_struct(&kv);

    assert!(lfs.is_err())
}

#[test]
fn test_roundtrip_lfs_content() {
    let oid = Sha256::from_str("27c0a92fc51290e3227bea4dd9e780c5035f017de8d5ddfa35b269ed82226d97")
        .unwrap();
    let size = 10;

    let generated_file = File::generate_lfs_file(oid, size, None).unwrap();
    let lfs_struct = File::data_only(generated_file).get_lfs_content().unwrap();

    let expected_lfs_struct = LFSContent::new(
        "https://git-lfs.github.com/spec/v1".to_string(),
        oid,
        size,
        None,
    );
    assert_eq!(lfs_struct, expected_lfs_struct)
}

quickcheck! {
    fn copy_info_roundtrip(
        copy_info: Option<(MPath, HgFileNodeId)>,
        file_bytes: FileBytes
    ) -> bool {
        let mut buf = Vec::new();
        let result = File::generate_metadata(copy_info.as_ref(), &file_bytes, &mut buf)
            .and_then(|_| {
                File::extract_copied_from(&buf)
            });
        match result {
            Ok(out_copy_info) => copy_info == out_copy_info,
            _ => {
                false
            }
        }
    }

    fn lfs_copy_info_roundtrip(
        oid: Sha256,
        size: u64,
        copy_from: Option<(MPath, HgFileNodeId)>
    ) -> bool {
        let result = File::generate_lfs_file(oid, size, copy_from.clone())
            .and_then(|bytes| File::data_only(bytes).get_lfs_content());

        match result {
            Ok(result) => result.oid() == oid && result.size() == size && result.copy_from() == copy_from,
            _ => false,
        }
    }
}

#[test]
fn test_hashes_are_unique() -> Result<(), Error> {
    let mut h = HashSet::new();

    for content_id in [ONES_CTID, TWOS_CTID] {
        for p1 in [Some(ONES_FNID), Some(TWOS_FNID), None] {
            for p2 in [Some(THREES_FNID), Some(FOURS_FNID), None] {
                let path1 = RepoPath::file("path")?
                    .into_mpath()
                    .ok_or_else(|| Error::msg("path1"))?;

                let path2 = RepoPath::file("path/2")?
                    .into_mpath()
                    .ok_or_else(|| Error::msg("path2"))?;

                let path3 = RepoPath::file("path2")?
                    .into_mpath()
                    .ok_or_else(|| Error::msg("path3"))?;

                for copy_path in [path1, path2, path3] {
                    for copy_parent in [ONES_FNID, TWOS_FNID, THREES_FNID] {
                        let copy_info = Some((copy_path.clone(), copy_parent.clone()));

                        let ptr = FileNodeIdPointer::new(&content_id, &copy_info, &p1, &p2);
                        assert!(!h.contains(&ptr), "Duplicate entry: {:?}", ptr);
                        h.insert(ptr);

                        if p1 == p2 {
                            continue;
                        }

                        let ptr = FileNodeIdPointer::new(&content_id, &copy_info, &p2, &p1);
                        assert!(!h.contains(&ptr), "Duplicate entry: {:?}", ptr);
                        h.insert(ptr);
                    }
                }

                let ptr = FileNodeIdPointer::new(&content_id, &None, &p1, &p2);
                assert!(!h.contains(&ptr), "Duplicate entry: {:?}", ptr);
                h.insert(ptr);

                if p1 == p2 {
                    continue;
                }

                let ptr = FileNodeIdPointer::new(&content_id, &None, &p2, &p1);
                assert!(!h.contains(&ptr), "Duplicate entry: {:?}", ptr);
                h.insert(ptr);
            }
        }
    }

    Ok(())
}