/*
 * 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 anyhow::Error;
use anyhow::Result;
use edenapi_types::WireHistoryEntry;
use mononoke_types::MPath;
use types::Parents;
use types::RepoPathBuf as ClientRepoPathBuf;

use crate::blobnode::HgParents;
use crate::nodehash::HgChangesetId;
use crate::nodehash::HgFileNodeId;
use crate::nodehash::NULL_HASH;

/// Mercurial revlogs and remotefilelog pack files have different formats of storing parents
/// if a file was copied or moved. This function converts from mercurial revlog format to
/// remotefilelog format
pub fn convert_parents_to_remotefilelog_format<'a>(
    parents: &HgParents,
    copyfrom: Option<&'a (MPath, HgFileNodeId)>,
) -> (HgFileNodeId, HgFileNodeId, Option<&'a MPath>) {
    let (p1, p2) = match parents {
        HgParents::None => (NULL_HASH, NULL_HASH),
        HgParents::One(p) => (p.clone(), NULL_HASH),
        HgParents::Two(p1, p2) => (p1.clone(), p2.clone()),
    };

    if let Some((ref copied_from, copied_rev)) = copyfrom {
        // Mercurial has a complicated copy/renames logic.
        // If (path1, filenode1) is copied/renamed from (path2, filenode2),
        // filenode1's p1 is set to filenode2, and copy_from path is set to path2
        // filenode1's p2 is null for non-merge commits. It might be non-null for merges.
        (*copied_rev, HgFileNodeId::new(p1), Some(copied_from))
    } else {
        (HgFileNodeId::new(p1), HgFileNodeId::new(p2), None)
    }
}

/// Represents a file history entry in Mercurial's loose file format.
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct HgFileHistoryEntry {
    node: HgFileNodeId,
    parents: HgParents,
    linknode: HgChangesetId,
    copyfrom: Option<(MPath, HgFileNodeId)>,
}

impl HgFileHistoryEntry {
    pub fn new(
        node: HgFileNodeId,
        parents: HgParents,
        linknode: HgChangesetId,
        copyfrom: Option<(MPath, HgFileNodeId)>,
    ) -> Self {
        Self {
            node,
            parents,
            linknode,
            copyfrom,
        }
    }

    pub fn filenode(&self) -> &HgFileNodeId {
        &self.node
    }

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

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

    pub fn copyfrom(&self) -> &Option<(MPath, HgFileNodeId)> {
        &self.copyfrom
    }
}

impl TryFrom<HgFileHistoryEntry> for WireHistoryEntry {
    type Error = Error;
    /// Convert from a representation of a history entry using Mononoke's types to
    /// a representation that uses the Mercurial client's types.
    fn try_from(entry: HgFileHistoryEntry) -> Result<Self> {
        let node = entry.node.into_nodehash().into();
        let linknode = entry.linknode.into_nodehash().into();

        let (parents, copyfrom) = match entry.copyfrom {
            Some((copypath, copyrev)) => {
                let copypath = ClientRepoPathBuf::from_utf8(copypath.to_vec())?;
                let copyrev = copyrev.into_nodehash().into();
                let (p1, _) = Parents::from(entry.parents).into_nodes();
                let parents = Parents::new(copyrev, p1);
                (parents, Some(copypath))
            }
            None => (entry.parents.into(), None),
        };

        Ok(Self {
            node,
            parents,
            linknode,
            copyfrom,
        })
    }
}


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

// Definitions for interfacing with SQL data stores

use sql::mysql_async::prelude::ConvIr;
use sql::mysql_async::prelude::FromValue;
use sql::mysql_async::FromValueError;
use sql::mysql_async::Value;

use crate::HgChangesetId;
use crate::HgFileNodeId;
use crate::HgNodeHash;

type FromValueResult<T> = Result<T, FromValueError>;

impl From<HgFileNodeId> for Value {
    fn from(id: HgFileNodeId) -> Self {
        Value::Bytes(id.into_nodehash().0.as_ref().into())
    }
}

impl From<HgChangesetId> for Value {
    fn from(id: HgChangesetId) -> Self {
        Value::Bytes(id.into_nodehash().0.as_ref().into())
    }
}

pub trait FromNodeHash {
    fn from_nodehash(hash: HgNodeHash) -> Self;
}

impl FromNodeHash for HgFileNodeId {
    fn from_nodehash(hash: HgNodeHash) -> Self {
        HgFileNodeId::new(hash)
    }
}

impl FromNodeHash for HgChangesetId {
    fn from_nodehash(hash: HgNodeHash) -> Self {
        HgChangesetId::new(hash)
    }
}

impl<T: FromNodeHash> ConvIr<T> for HgNodeHash {
    fn new(v: Value) -> FromValueResult<Self> {
        match v {
            Value::Bytes(bytes) => {
                HgNodeHash::from_bytes(&bytes).map_err(move |_| FromValueError(Value::Bytes(bytes)))
            }
            v => Err(FromValueError(v)),
        }
    }

    fn commit(self) -> T {
        T::from_nodehash(self)
    }

    fn rollback(self) -> Value {
        Value::Bytes(self.0.as_ref().into())
    }
}

impl FromValue for HgFileNodeId {
    type Intermediate = HgNodeHash;
}

impl FromValue for HgChangesetId {
    type Intermediate = HgNodeHash;
}


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

use mononoke_types::MPath;

use crate::fsencode::fncache_fsencode;
use crate::Globalrev;

fn check_fsencode_with_dotencode(path: &[u8], expected: &str) {
    let mut elements = vec![];
    let path = &MPath::new(path).unwrap();
    elements.extend(path.into_iter().cloned());

    let elements = elements.iter().map(|e| e.as_ref()).collect::<Vec<_>>();
    assert_eq!(fncache_fsencode(&elements, true), PathBuf::from(expected));
}

#[test]
fn test_parse_svnrev() {
    let to_parse = "svn:uuid/path@1234";
    assert_eq!(Globalrev::parse_svnrev(to_parse).unwrap(), 1234);

    let to_parse = "svn:uuid@/pa@th@1";
    assert_eq!(Globalrev::parse_svnrev(to_parse).unwrap(), 1);

    let to_parse = "svn:uuid/path1234";
    Globalrev::parse_svnrev(to_parse).expect_err("convert_revision without @ should fail");

    let to_parse = "svn:uuid/path@1234a";
    Globalrev::parse_svnrev(to_parse).expect_err("1234a shouldn't parse to int successfully");
}

#[test]
fn test_fsencode_from_core_hg() {
    let toencode = b"data/abcdefghijklmnopqrstuvwxyz0123456789 !#%&'()+,-.;=[]^`{}";
    let expected = "data/abcdefghijklmnopqrstuvwxyz0123456789 !#%&'()+,-.;=[]^`{}";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/abcdefghijklmnopqrstuvwxyz0123456789 !#%&'()+,-.;=[]^`{}xxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "data/abcdefghijklmnopqrstuvwxyz0123456789 !#%&'()+,-.;=[]^`{}xxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/\"23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/~2223456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxfc7e3ec7b0687ee06ed8c32fef0eb0c1980259f5";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/less <, greater >, colon :, double-quote \", backslash \\, pipe |, question-mark ?, asterisk *";
    let expected = "data/less ~3c, greater ~3e, colon ~3a, double-quote ~22, backslash ~5c, pipe ~7c, question-mark ~3f, asterisk ~2a";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    let expected = "data/_a_b_c_d_e_f_g_h_i_j_k_l_m_n_o_p_q_r_s_t_u_v_w_x_y_z";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/_";
    let expected = "data/__";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/~";
    let expected = "data/~7e";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/\x02\x03\x04\x05\x06\x07\x08\t\x0b\x0c\r\x0e\x0f\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f";
    let expected =
        "data/~02~03~04~05~06~07~08~09~0b~0c~0d~0e~0f~11~12~13~14~15~16~17~18~19~1a~1b~1c~1d~1e~1f";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f";
    let expected = "data/~7e~7f~80~81~82~83~84~85~86~87~88~89~8a~8b~8c~8d~8e~8f~90~91~92~93~94~95~96~97~98~99~9a~9b~9c~9d~9e~9f";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf";
    let expected = "data/~a0~a1~a2~a3~a4~a5~a6~a7~a8~a9~aa~ab~ac~ad~ae~af~b0~b1~b2~b3~b4~b5~b6~b7~b8~b9~ba~bb~bc~bd~be~bf";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf";
    let expected = "data/~c0~c1~c2~c3~c4~c5~c6~c7~c8~c9~ca~cb~cc~cd~ce~cf~d0~d1~d2~d3~d4~d5~d6~d7~d8~d9~da~db~dc~dd~de~df";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff";
    let expected = "data/~e0~e1~e2~e3~e4~e5~e6~e7~e8~e9~ea~eb~ec~ed~ee~ef~f0~f1~f2~f3~f4~f5~f6~f7~f8~f9~fa~fb~fc~fd~fe~ff";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/x.h.i/x.hg/x.i/x.d/foo";
    let expected = "data/x.h.i.hg/x.hg.hg/x.i.hg/x.d.hg/foo";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/a.hg/a.i/a.d/foo";
    let expected = "data/a.hg.hg/a.i.hg/a.d.hg/foo";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/au.hg/au.i/au.d/foo";
    let expected = "data/au.hg.hg/au.i.hg/au.d.hg/foo";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/aux.hg/aux.i/aux.d/foo";
    let expected = "data/au~78.hg.hg/au~78.i.hg/au~78.d.hg/foo";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/auxy.hg/auxy.i/auxy.d/foo";
    let expected = "data/auxy.hg.hg/auxy.i.hg/auxy.d.hg/foo";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/foo/x.hg";
    let expected = "data/foo/x.hg";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/foo/x.i";
    let expected = "data/foo/x.i";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/foo/x.d";
    let expected = "data/foo/x.d";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/foo/a.hg";
    let expected = "data/foo/a.hg";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/foo/a.i";
    let expected = "data/foo/a.i";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/foo/a.d";
    let expected = "data/foo/a.d";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/foo/au.hg";
    let expected = "data/foo/au.hg";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/foo/au.i";
    let expected = "data/foo/au.i";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/foo/au.d";
    let expected = "data/foo/au.d";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/foo/aux.hg";
    let expected = "data/foo/au~78.hg";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/foo/aux.i";
    let expected = "data/foo/au~78.i";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/foo/aux.d";
    let expected = "data/foo/au~78.d";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/foo/auxy.hg";
    let expected = "data/foo/auxy.hg";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/foo/auxy.i";
    let expected = "data/foo/auxy.i";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/foo/auxy.d";
    let expected = "data/foo/auxy.d";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/.hg/.i/.d/foo";
    let expected = "data/~2ehg.hg/~2ei.hg/~2ed.hg/foo";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/aux.bla/bla.aux/prn/PRN/lpt/com3/nul/coma/foo.NUL/normal.c.i";
    let expected =
        "data/au~78.bla/bla.aux/pr~6e/_p_r_n/lpt/co~6d3/nu~6c/coma/foo._n_u_l/normal.c.i";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/AUX/SECOND/X.PRN/FOURTH/FI:FTH/SIXTH/SEVENTH/EIGHTH/NINETH/TENTH/ELEVENTH/LOREMIPSUM.TXT.i";
    let expected = "dh/au~78/second/x.prn/fourth/fi~3afth/sixth/seventh/eighth/nineth/tenth/loremia20419e358ddff1bf8751e38288aff1d7c32ec05.i";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/enterprise/openesbaddons/contrib-imola/corba-bc/netbeansplugin/wsdlExtension/src/main/java/META-INF/services/org.netbeans.modules.xml.wsdl.bindingsupport.spi.ExtensibilityElementTemplateProvider.i";
    let expected = "dh/enterpri/openesba/contrib-/corba-bc/netbeans/wsdlexte/src/main/java/org.net7018f27961fdf338a598a40c4683429e7ffb9743.i";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/AUX.THE-QUICK-BROWN-FOX-JU:MPS-OVER-THE-LAZY-DOG-THE-QUICK-BROWN-FOX-JUMPS-OVER-THE-LAZY-DOG.TXT.i";
    let expected = "dh/au~78.the-quick-brown-fox-ju~3amps-over-the-lazy-dog-the-quick-brown-fox-jud4dcadd033000ab2b26eb66bae1906bcb15d4a70.i";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/Project Planning/Resources/AnotherLongDirectoryName/Followedbyanother/AndAnother/AndThenAnExtremelyLongFileName.txt";
    let expected = "dh/project_/resource/anotherl/followed/andanoth/andthenanextremelylongfilenaf93030515d9849cfdca52937c2204d19f83913e5.txt";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/Project.Planning/Resources/AnotherLongDirectoryName/Followedbyanother/AndAnother/AndThenAnExtremelyLongFileName.txt";
    let expected = "dh/project_/resource/anotherl/followed/andanoth/andthenanextremelylongfilena0fd7c506f5c9d58204444fc67e9499006bd2d445.txt";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/foo.../foo   / /a./_. /__/.x../    bla/.FOO/something.i";
    let expected =
        "data/foo..~2e/foo  ~20/~20/a~2e/__.~20/____/~2ex.~2e/~20   bla/~2e_f_o_o/something.i";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/c/co/com/com0/com1/com2/com3/com4/com5/com6/com7/com8/com9";
    let expected =
        "data/c/co/com/com0/co~6d1/co~6d2/co~6d3/co~6d4/co~6d5/co~6d6/co~6d7/co~6d8/co~6d9";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/C/CO/COM/COM0/COM1/COM2/COM3/COM4/COM5/COM6/COM7/COM8/COM9";
    let expected = "data/_c/_c_o/_c_o_m/_c_o_m0/_c_o_m1/_c_o_m2/_c_o_m3/_c_o_m4/_c_o_m5/_c_o_m6/_c_o_m7/_c_o_m8/_c_o_m9";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/c.x/co.x/com.x/com0.x/com1.x/com2.x/com3.x/com4.x/com5.x/com6.x/com7.x/com8.x/com9.x";
    let expected = "data/c.x/co.x/com.x/com0.x/co~6d1.x/co~6d2.x/co~6d3.x/co~6d4.x/co~6d5.x/co~6d6.x/co~6d7.x/co~6d8.x/co~6d9.x";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode =
        b"data/x.c/x.co/x.com0/x.com1/x.com2/x.com3/x.com4/x.com5/x.com6/x.com7/x.com8/x.com9";
    let expected =
        "data/x.c/x.co/x.com0/x.com1/x.com2/x.com3/x.com4/x.com5/x.com6/x.com7/x.com8/x.com9";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/cx/cox/comx/com0x/com1x/com2x/com3x/com4x/com5x/com6x/com7x/com8x/com9x";
    let expected = "data/cx/cox/comx/com0x/com1x/com2x/com3x/com4x/com5x/com6x/com7x/com8x/com9x";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/xc/xco/xcom0/xcom1/xcom2/xcom3/xcom4/xcom5/xcom6/xcom7/xcom8/xcom9";
    let expected = "data/xc/xco/xcom0/xcom1/xcom2/xcom3/xcom4/xcom5/xcom6/xcom7/xcom8/xcom9";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/l/lp/lpt/lpt0/lpt1/lpt2/lpt3/lpt4/lpt5/lpt6/lpt7/lpt8/lpt9";
    let expected =
        "data/l/lp/lpt/lpt0/lp~741/lp~742/lp~743/lp~744/lp~745/lp~746/lp~747/lp~748/lp~749";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/L/LP/LPT/LPT0/LPT1/LPT2/LPT3/LPT4/LPT5/LPT6/LPT7/LPT8/LPT9";
    let expected = "data/_l/_l_p/_l_p_t/_l_p_t0/_l_p_t1/_l_p_t2/_l_p_t3/_l_p_t4/_l_p_t5/_l_p_t6/_l_p_t7/_l_p_t8/_l_p_t9";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/l.x/lp.x/lpt.x/lpt0.x/lpt1.x/lpt2.x/lpt3.x/lpt4.x/lpt5.x/lpt6.x/lpt7.x/lpt8.x/lpt9.x";
    let expected = "data/l.x/lp.x/lpt.x/lpt0.x/lp~741.x/lp~742.x/lp~743.x/lp~744.x/lp~745.x/lp~746.x/lp~747.x/lp~748.x/lp~749.x";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/x.l/x.lp/x.lpt/x.lpt0/x.lpt1/x.lpt2/x.lpt3/x.lpt4/x.lpt5/x.lpt6/x.lpt7/x.lpt8/x.lpt9";
    let expected =
        "data/x.l/x.lp/x.lpt/x.lpt0/x.lpt1/x.lpt2/x.lpt3/x.lpt4/x.lpt5/x.lpt6/x.lpt7/x.lpt8/x.lpt9";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/lx/lpx/lptx/lpt0x/lpt1x/lpt2x/lpt3x/lpt4x/lpt5x/lpt6x/lpt7x/lpt8x/lpt9x";
    let expected = "data/lx/lpx/lptx/lpt0x/lpt1x/lpt2x/lpt3x/lpt4x/lpt5x/lpt6x/lpt7x/lpt8x/lpt9x";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/xl/xlp/xlpt/xlpt0/xlpt1/xlpt2/xlpt3/xlpt4/xlpt5/xlpt6/xlpt7/xlpt8/xlpt9";
    let expected = "data/xl/xlp/xlpt/xlpt0/xlpt1/xlpt2/xlpt3/xlpt4/xlpt5/xlpt6/xlpt7/xlpt8/xlpt9";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/con/p/pr/prn/a/au/aux/n/nu/nul";
    let expected = "data/co~6e/p/pr/pr~6e/a/au/au~78/n/nu/nu~6c";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/CON/P/PR/PRN/A/AU/AUX/N/NU/NUL";
    let expected = "data/_c_o_n/_p/_p_r/_p_r_n/_a/_a_u/_a_u_x/_n/_n_u/_n_u_l";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/con.x/p.x/pr.x/prn.x/a.x/au.x/aux.x/n.x/nu.x/nul.x";
    let expected = "data/co~6e.x/p.x/pr.x/pr~6e.x/a.x/au.x/au~78.x/n.x/nu.x/nu~6c.x";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/x.con/x.p/x.pr/x.prn/x.a/x.au/x.aux/x.n/x.nu/x.nul";
    let expected = "data/x.con/x.p/x.pr/x.prn/x.a/x.au/x.aux/x.n/x.nu/x.nul";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/conx/px/prx/prnx/ax/aux/auxx/nx/nux/nulx";
    let expected = "data/conx/px/prx/prnx/ax/au~78/auxx/nx/nux/nulx";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/xcon/xp/xpr/xprn/xa/xau/xaux/xn/xnu/xnul";
    let expected = "data/xcon/xp/xpr/xprn/xa/xau/xaux/xn/xnu/xnul";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/a./au./aux./auxy./aux.";
    let expected = "data/a~2e/au~2e/au~78~2e/auxy~2e/au~78~2e";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/c./co./con./cony./con.";
    let expected = "data/c~2e/co~2e/co~6e~2e/cony~2e/co~6e~2e";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/p./pr./prn./prny./prn.";
    let expected = "data/p~2e/pr~2e/pr~6e~2e/prny~2e/pr~6e~2e";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/n./nu./nul./nuly./nul.";
    let expected = "data/n~2e/nu~2e/nu~6c~2e/nuly~2e/nu~6c~2e";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/l./lp./lpt./lpt1./lpt1y./lpt1.";
    let expected = "data/l~2e/lp~2e/lpt~2e/lp~741~2e/lpt1y~2e/lp~741~2e";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/lpt9./lpt9y./lpt9.";
    let expected = "data/lp~749~2e/lpt9y~2e/lp~749~2e";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/com./com1./com1y./com1.";
    let expected = "data/com~2e/co~6d1~2e/com1y~2e/co~6d1~2e";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/com9./com9y./com9.";
    let expected = "data/co~6d9~2e/com9y~2e/co~6d9~2e";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/a /au /aux /auxy /aux ";
    let expected = "data/a~20/au~20/aux~20/auxy~20/aux~20";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/123456789-123456789-123456789-123456789-123456789-unhashed--xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "data/123456789-123456789-123456789-123456789-123456789-unhashed--xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/123456789-123456789-123456789-123456789-123456789-hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/123456789-123456789-123456789-123456789-123456789-hashed----xxxxxxxxx-xxxxxxxe9c55002b50bf5181e7a6fc1f60b126e2a6fcf71";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/123456789-123456789-123456789-123456789-123456789-hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxy-123456789-123456";
    let expected = "dh/123456789-123456789-123456789-123456789-123456789-hashed----xxxxxxxxx-xxxxxxxd24fa4455faf8a94350c18e5eace7c2bb17af706";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/A23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/a23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxcbbc657029b41b94ed510d05feb6716a5c03bc6b";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/Z23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/z23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxx938f32a725c89512833fb96b6602dd9ebff51ddd";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/a23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "data/a23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/z23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "data/z23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/_23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/_23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxx9921a01af50feeabc060ce00eee4cba6efc31d2b";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/~23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/~7e23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx9cec6f97d569c10995f785720044ea2e4227481b";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/<23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/~3c23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxee67d8f275876ca1ef2500fc542e63c885c4e62d";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/>23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/~3e23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx387a85a5b1547cc9136310c974df716818458ddb";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/:23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/~3a23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx2e4154fb571d13d22399c58cc4ef4858e4b75999";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/\\23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/~5c23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx944e1f2b7110687e116e0d151328ac648b06ab4a";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/|23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/~7c23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx28b23dd3fd0242946334126ab62bcd772aac32f4";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/?23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/~3f23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxa263022d3994d2143d98f94f431eef8b5e7e0f8a";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/*23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/~2a23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx0e7e6020e3c00ba7bb7893d84ca2966fbf53e140";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/ 23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/~2023456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx92acbc78ef8c0b796111629a02601f07d8aec4ea";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/.23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/~2e23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxdbe19cc6505b3515ab9228cebf877ad07075168f";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/123456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-1234 ";
    let expected = "dh/123456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxx0025dc73e04f97426db4893e3bf67d581dc6d066";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/123456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-1234.";
    let expected = "dh/123456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxx85a16cf03ee7feba8a5abc626f1ba9886d01e89d";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/ x/456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/~20x/456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx1b3a3b712b2ac00d6af14ae8b4c14fdbf904f516";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/.x/456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/~2ex/456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx39dbc4c193a5643a8936fc69c3363cd7ac91ab14";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/x /456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/x~20/456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx2253c341df0b5290790ad312cd8499850f2273e5";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/x./456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/x~2e/456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxcc0324d696d34562b44b5138db08ee1594ccc583";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/x.i/56789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/x.i.hg/56789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxa4c4399bdf81c67dbbbb7060aa0124d8dea94f74";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/x.d/56789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/x.d.hg/56789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxx1303fa90473b230615f5b3ea7b660e881ae5270a";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/x.hg/5789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/x.hg.hg/5789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxx26d724a8af68e7a4e4455e6602ea9adbd0eb801f";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/con/56789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/co~6e/56789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxc0794d4f4c605a2617900eb2563d7113cf6ea7d3";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/prn/56789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/pr~6e/56789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx64db876e1a9730e27236cb9b167aff942240e932";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/aux/56789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/au~78/56789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx8a178558405ca6fb4bbd75446dfa186f06751a0d";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/nul/56789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/nu~6c/56789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxc5e51b6fec1bd07bd243b053a0c3f7209855b886";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/com1/6789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/co~6d1/6789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx32f5f44ece3bb62b9327369ca84cc19c86259fcd";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/com9/6789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/co~6d9/6789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx734360b28c66a3230f55849fe8926206d229f990";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/lpt1/6789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/lp~741/6789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxe6f16ab4b6b0637676b2842b3345c9836df46ef7";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/lpt9/6789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "dh/lp~749/6789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxa475814c51acead3e44f2ff801f0c4903f986157";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/123456789-123456789-123456789-123456789-123456789-/com/com0/lpt/lpt0/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    let expected = "data/123456789-123456789-123456789-123456789-123456789-/com/com0/lpt/lpt0/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/-123456789-123456789-123456789-123456789-hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/-123456789-123456789-123456789-123456789-hashed----xxxxxxxxx-xxxxxxx4e9e9e384d00929a93b6835fbf976eb32321ff3c";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/123456789/123456789-123456789-123456789-123456789-hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/123456789-123456789-123456789-123456789-hashed----xxxxxxxxx-xxxxxxxx1f4e4ec5f2be76e109bfaa8e31c062fe426d5490";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/9-123456789-123456789-123456789-hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/9-123456789-123456789-123456789-hashed----xxxxxxxxx-xxxxxxx3332d8329d969cf835542a9f2cbcfb385b6cf39d";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/123456789/123456789/123456789-123456789-123456789-hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/123456789-123456789-123456789-hashed----xxxxxxxxx-xxxxxxxxx9699559798247dffa18717138859be5f8874840e";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/89-123456789-123456789-hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/12345678/89-123456789-123456789-hashed----xxxxxxxxx-xxxxxxxf0a2b053bb1369cce02f78c217d6a7aaea18c439";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/123456789/123456789/123456789/123456789-123456789-hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/12345678/123456789-123456789-hashed----xxxxxxxxx-xxxxxxxxx-1c6f8284967384ec13985a046d3553179d9d03cd";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/789-123456789-hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/12345678/12345678/789-123456789-hashed----xxxxxxxxx-xxxxxxx0d30c99049d8f0ff97b94d4ef302027e8d54c6fd";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/123456789/123456789/123456789/123456789/123456789-hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/12345678/12345678/123456789-hashed----xxxxxxxxx-xxxxxxxxx-x46162779e1a771810b37a737f82ae7ed33771402";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678/6789-hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/6789-hashed----xxxxxxxxx-xxxxxxxbfe752ddc8b003c2790c66a9f2eb1ea75c114390";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/123456789/123456789/123456789/123456789/123456789/hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/hashed----xxxxxxxxx-xxxxxxxxx-xxb94c27b3532fa880cdd572b1c514785cab7b6ff2";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678/12345678/ed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/ed----xxxxxxxxx-xxxxxxxcd8cc5483a0f3be409e0e5d4bf9e36e113c59235";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/123456789/123456789/123456789/123456789/123456789/123456789/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/xxxxxxxxx-xxxxxxxxx-xxx47dd6f616f833a142da00701b334cebbf640da06";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/xxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/xxxxxx-xxxxxxx1c8ed635229fc22efe51035feeadeb4c8a0ecb82";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/123456789/123456789/123456789/123456789/123456789/123456789/123456789/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/xxxxxxxxx-xxxx298ff7d33f8ce6db57930837ffea2fb2f48bb926";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/xxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/xxxxxxx-xxxxxxc8996ccd41b471f768057181a4d59d2febe7277d";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/xxxxxxxxx-xxxx4fa04a839a6bda93e1c21c713f2edcbd16e8890d";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxx4d43d1ccaa20efbfe99ec779dc063611536ff2c5";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678x/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxxxx0f9efce65189cc60fd90fe4ffd49d7b58bbe0f2e";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678x/12345678/12345678/12345678/12345678/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxxxx945ca395708cafdd54a94501859beabd3e243921";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678x/12345678/12345678/12345678/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxxxxac62bf6898c4fd0502146074547c11caa751a327";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678x/12345678/12345678/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxxxx2ae5a2baed7983fae8974d0ca06c6bf08b9aee92";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678x/12345678/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxxxx214aba07b6687532a43d1e9eaf6e88cfca96b68c";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678/12345678x/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxxxxe7a022ae82f0f55cf4e0498e55ba59ea4ebb55bf";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678/12345678/12345678x/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxxxxb51ce61164996a80f36ce3cfe64b62d519aedae3";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/123456/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/xxxxxxxxx-xxxx11fa9873cc6c3215eae864528b5530a04efc6cfe";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/1234./-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/-xxxxxxxxx-xxx602df9b45bec564e2e1f0645d5140dddcc76ed58";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/1234 /-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/-xxxxxxxxx-xxxd99ff212bc84b4d1f70cd6b0071e3ef69d4e12ce";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12./xx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12~2e/xx-xxxxx7baeb5ed7f14a586ee1cacecdbcbff70032d1b3c";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12 /xx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12~20/xx-xxxxxcf79ca9795f77d7f75745da36807e5d772bd5182";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.345.i";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxc10ad03b5755ed524f5286aab1815dfe07729438.i";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.345.d";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxx9eec83381f2b39ef5ac8b4ecdf2c94f7983f57c8.d";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456.i";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxb7796dc7d175cfb0bb8a7728f58f6ebec9042568.i";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.34567.i";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxb515857a6bfeef017c4894d8df42458ac65d55b8.i";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.345678.i";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxb05a0f247bc0a776211cd6a32ab714fd9cc09f2b.i";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789.i";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxf192b48bff08d9e0e12035fb52bc58c70de72c94.i";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789-.i";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxx435551e0ed4c7b083b9ba83cee916670e02e80ad.i";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789-1.i";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxa7f74eb98d8d58b716356dfd26e2f9aaa65d6a9a.i";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789-12.i";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxed68d9bd43b931f0b100267fee488d65a0c66f62.i";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789-123.i";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxx5cea44de2b642d2ba2b4a30693ffb1049644d698.i";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789-1234.i";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxx68462f62a7f230b39c1b5400d73ec35920990b7e.i";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789-12345.i";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxx4cb852a314c6da240a83eec94761cdd71c6ec22e.i";
    check_fsencode_with_dotencode(&toencode[..], expected);

    let toencode = b"data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789-12345-ABCDEFGHIJKLMNOPRSTUVWXYZ-abcdefghjiklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPRSTUVWXYZ-1234567890-xxxxxxxxx-xxxxxxxxx-xxxxxxxx-xxxxxxxxx-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww.i";
    let expected = "dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxx0c9ce54a93e391e5768b1b4762ec3cb3ca2777d0.i";
    check_fsencode_with_dotencode(&toencode[..], 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.
 */

use percent_encoding::utf8_percent_encode;
use percent_encoding::AsciiSet;
use percent_encoding::CONTROLS;

// https://url.spec.whatwg.org/#fragment-percent-encode-set
const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
// https://url.spec.whatwg.org/#path-percent-encode-set
const PATH: &AsciiSet = &FRAGMENT.add(b'#').add(b'?').add(b'{').add(b'}');
const USERINFO: &AsciiSet = &PATH
    .add(b'/')
    .add(b':')
    .add(b';')
    .add(b'=')
    .add(b'@')
    .add(b'[')
    .add(b'\\')
    .add(b']')
    .add(b'^')
    .add(b'|');

pub const HG_ENCODE_SET: &AsciiSet = &USERINFO.add(b',');

pub fn percent_encode(input: &str) -> String {
    // This encode set doesn't exactly match what Python's urllib does, but it's
    // close enough and importantly it encodes '=' which is the only important
    // one.
    utf8_percent_encode(input, HG_ENCODE_SET).collect::<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.
 */

use anyhow::Result;

use super::file::File;
use crate::blobs::HgBlobManifest;
use crate::HgFileEnvelope;
use crate::HgFileNodeId;
use crate::HgManifestEnvelope;
use crate::HgParents;
use crate::MPath;

pub trait HgBlobEnvelope: Send + Sync + 'static {
    fn get_parents(&self) -> HgParents;
    fn get_copy_info(&self) -> Result<Option<(MPath, HgFileNodeId)>>;
    fn get_size(&self) -> Option<u64>;
}

impl HgBlobEnvelope for HgFileEnvelope {
    fn get_parents(&self) -> HgParents {
        let (p1, p2) = self.parents();
        HgParents::new(
            p1.map(HgFileNodeId::into_nodehash),
            p2.map(HgFileNodeId::into_nodehash),
        )
    }

    fn get_copy_info(&self) -> Result<Option<(MPath, HgFileNodeId)>> {
        File::extract_copied_from(self.metadata())
    }

    fn get_size(&self) -> Option<u64> {
        Some(self.content_size())
    }
}

impl HgBlobEnvelope for HgManifestEnvelope {
    fn get_parents(&self) -> HgParents {
        let (p1, p2) = self.parents();
        HgParents::new(p1, p2)
    }

    fn get_copy_info(&self) -> Result<Option<(MPath, HgFileNodeId)>> {
        Ok(None)
    }

    fn get_size(&self) -> Option<u64> {
        None
    }
}

impl HgBlobEnvelope for HgBlobManifest {
    fn get_parents(&self) -> HgParents {
        self.hg_parents()
    }

    fn get_copy_info(&self) -> Result<Option<(MPath, HgFileNodeId)>> {
        Ok(None)
    }

    fn get_size(&self) -> Option<u64> {
        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 mononoke_types::ContentId;
use mononoke_types::RepoPath;
use thiserror::Error;

use crate::HgFileNodeId;
use crate::HgNodeHash;
use crate::Type;

#[derive(Debug, Error)]
pub enum ErrorKind {
    #[error("Corrupt hg filenode returned: {expected} != {actual}")]
    CorruptHgFileNode {
        expected: HgFileNodeId,
        actual: HgFileNodeId,
    },
    #[error("Content blob missing for id: {0}")]
    ContentBlobMissing(ContentId),
    #[error("Mercurial content missing for node {0} (type {1})")]
    HgContentMissing(HgNodeHash, Type),
    #[error("Error while deserializing file node retrieved from key '{0}'")]
    FileNodeDeserializeFailed(String),
    #[error("Error while deserializing manifest retrieved from key '{0}'")]
    ManifestDeserializeFailed(String),
    #[error("Incorrect LFS file content {0}")]
    IncorrectLfsFileContent(String),
    #[error("Inconsistent node hash for entry: path {0}, provided: {1}, computed: {2}")]
    InconsistentEntryHashForPath(RepoPath, HgNodeHash, HgNodeHash),
    #[error("Inconsistent node hash for entry: provided: {0}, computed: {1}")]
    InconsistentEntryHash(HgNodeHash, HgNodeHash),
}


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

//! Plain files, symlinks

use std::collections::HashMap;
use std::io::Write;
use std::str;
use std::str::FromStr;

use anyhow::Result;
use async_trait::async_trait;
use blobstore::Blobstore;
use blobstore::Loadable;
use blobstore::LoadableError;
use bytes::Bytes;
use context::CoreContext;
use itertools::Itertools;
use mononoke_types::hash::Sha256;

use super::errors::ErrorKind;
use crate::FileBytes;
use crate::HgBlob;
use crate::HgBlobNode;
use crate::HgFileEnvelope;
use crate::HgFileNodeId;
use crate::MPath;

#[async_trait]
impl Loadable for HgFileNodeId {
    type Value = HgFileEnvelope;

    async fn load<'a, B: Blobstore>(
        &'a self,
        ctx: &'a CoreContext,
        blobstore: &'a B,
    ) -> Result<Self::Value, LoadableError> {
        let blobstore_key = self.blobstore_key();
        let bytes = blobstore.get(ctx, &blobstore_key).await?;
        let blobstore_bytes = match bytes {
            Some(bytes) => bytes,
            None => return Err(LoadableError::Missing(blobstore_key)),
        };
        Ok(HgFileEnvelope::from_blob(blobstore_bytes.into())?)
    }
}

/// A Mercurial file. Knows about its parents, and might content inline metadata.
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct File {
    node: HgBlobNode,
}

pub const META_MARKER: &[u8] = b"\x01\n";
const COPY_PATH_KEY: &[u8] = b"copy";
const COPY_REV_KEY: &[u8] = b"copyrev";
pub const META_SZ: usize = 2;

impl File {
    pub fn new<B: Into<HgBlob>>(
        blob: B,
        p1: Option<HgFileNodeId>,
        p2: Option<HgFileNodeId>,
    ) -> Self {
        let node = HgBlobNode::new(
            blob,
            p1.map(HgFileNodeId::into_nodehash),
            p2.map(HgFileNodeId::into_nodehash),
        );
        File { node }
    }

    // (there's a use case for not providing parents, so should parents not be inside the file?)
    #[inline]
    pub fn data_only<B: Into<HgBlob>>(blob: B) -> Self {
        Self::new(blob, None, None)
    }

    // Note that this function drops empty metadata. For lossless preservation, use the metadata
    // function instead.
    pub fn extract_meta(file: &[u8]) -> (&[u8], usize) {
        if file.len() < META_SZ {
            return (&[], 0);
        }
        if &file[..META_SZ] != META_MARKER {
            (&[], 0)
        } else {
            let metasz = &file[META_SZ..]
                .iter()
                .enumerate()
                .tuple_windows()
                .find(|&((_, a), (_, b))| *a == META_MARKER[0] && *b == META_MARKER[1])
                .map_or(META_SZ, |((idx, _), _)| idx + META_SZ * 2); // XXX malformed if None - unterminated metadata

            let metasz = *metasz;
            if metasz >= META_SZ * 2 {
                (&file[META_SZ..metasz - META_SZ], metasz)
            } else {
                (&[], metasz)
            }
        }
    }

    pub fn parse_to_hash_map<'a>(
        content: &'a [u8],
        delimiter: &[u8],
    ) -> HashMap<&'a [u8], &'a [u8]> {
        let mut kv = HashMap::new();
        let delimiter_len = delimiter.len();

        for line in content.split(|c| *c == b'\n') {
            if line.len() < delimiter_len {
                continue;
            }

            // split on "delimiter" - no quoting within key/value
            for idx in 0..line.len() - delimiter_len + 1 {
                if &line[idx..idx + delimiter_len] == delimiter {
                    kv.insert(&line[..idx], &line[idx + delimiter_len..]);
                    break;
                }
            }
        }
        kv
    }

    pub fn parse_meta(file: &[u8]) -> HashMap<&[u8], &[u8]> {
        let (meta, _) = Self::extract_meta(file);

        // Yay, Mercurial has yet another ad-hoc encoding. This one is kv pairs separated by \n,
        // with ": " separating the key and value
        Self::parse_to_hash_map(meta, &[b':', b' '])
    }

    pub fn parse_content_to_lfs_hash_map(content: &[u8]) -> HashMap<&[u8], &[u8]> {
        Self::parse_to_hash_map(content, &[b' '])
    }

    pub fn copied_from(&self) -> Result<Option<(MPath, HgFileNodeId)>> {
        let buf = self.node.as_blob().as_slice();
        Self::extract_copied_from(buf)
    }

    fn get_copied_from_with_keys(
        meta: &HashMap<&[u8], &[u8]>,
        copy_path_key: &'static [u8],
        copy_rev_key: &'static [u8],
    ) -> Result<Option<(MPath, HgFileNodeId)>> {
        let path = meta.get(copy_path_key).cloned().map(MPath::new);
        let nodeid = meta
            .get(copy_rev_key)
            .and_then(|rev| str::from_utf8(rev).ok())
            .and_then(|rev| rev.parse().map(HgFileNodeId::new).ok());
        match (path, nodeid) {
            (Some(Ok(path)), Some(nodeid)) => Ok(Some((path, nodeid))),
            (Some(Err(e)), _) => Err(e.context("invalid path in copy metadata")),
            _ => Ok(None),
        }
    }

    pub fn extract_copied_from(buf: &[u8]) -> Result<Option<(MPath, HgFileNodeId)>> {
        let meta = Self::parse_meta(buf);
        Self::get_copied_from_with_keys(&meta, COPY_PATH_KEY, COPY_REV_KEY)
    }

    pub fn generate_metadata<T>(
        copy_from: Option<&(MPath, HgFileNodeId)>,
        file_bytes: &FileBytes,
        buf: &mut T,
    ) -> Result<()>
    where
        T: Write,
    {
        match copy_from {
            None => {
                if file_bytes.starts_with(META_MARKER) {
                    // If the file contents starts with META_MARKER, the metadata must be
                    // written out to avoid ambiguity.
                    buf.write_all(META_MARKER)?;
                    buf.write_all(META_MARKER)?;
                }
            }
            Some((path, version)) => {
                buf.write_all(META_MARKER)?;
                buf.write_all(COPY_PATH_KEY)?;
                buf.write_all(b": ")?;
                path.generate(buf)?;
                buf.write_all(b"\n")?;

                buf.write_all(COPY_REV_KEY)?;
                buf.write_all(b": ")?;
                buf.write_all(version.to_hex().as_ref())?;
                buf.write_all(b"\n")?;
                buf.write_all(META_MARKER)?;
            }
        };
        Ok(())
    }

    pub fn content(&self) -> &[u8] {
        let data = self.node.as_blob().as_slice();
        let (_, off) = Self::extract_meta(data);
        &data[off..]
    }

    pub fn metadata(&self) -> Bytes {
        let data = self.node.as_blob().as_inner();
        let (_, off) = Self::extract_meta(data);
        data.slice(..off)
    }

    pub fn file_contents(&self) -> FileBytes {
        let data = self.node.as_blob().as_inner();
        let (_, off) = Self::extract_meta(data);
        FileBytes(data.slice(off..))
    }

    pub fn get_lfs_content(&self) -> Result<LFSContent> {
        let data = self.node.as_blob().as_inner();
        let (_, off) = Self::extract_meta(data);

        Self::get_lfs_struct(&Self::parse_content_to_lfs_hash_map(&data.slice(off..)))
    }

    fn parse_mandatory_lfs(contents: &HashMap<&[u8], &[u8]>) -> Result<(String, Sha256, u64)> {
        let version = contents
            .get(VERSION)
            .and_then(|s| str::from_utf8(s).ok())
            .map(|s| s.to_string())
            .ok_or_else(|| {
                ErrorKind::IncorrectLfsFileContent(
                    "VERSION mandatory field parsing failed in Lfs file content".to_string(),
                )
            })?;

        let oid = contents
            .get(OID)
            .and_then(|s| str::from_utf8(s).ok())
            .and_then(|s| {
                let prefix_len = SHA256_PREFIX.len();

                let check = prefix_len <= s.len() && s[..prefix_len].as_bytes() == SHA256_PREFIX;
                if check {
                    Some(s[prefix_len..].to_string())
                } else {
                    None
                }
            })
            .and_then(|s| Sha256::from_str(&s).ok())
            .ok_or_else(|| {
                ErrorKind::IncorrectLfsFileContent(
                    "OID mandatory field parsing failed in Lfs file content".to_string(),
                )
            })?;
        let size = contents
            .get(SIZE)
            .and_then(|s| str::from_utf8(s).ok())
            .and_then(|s| s.parse::<u64>().ok())
            .ok_or_else(|| {
                ErrorKind::IncorrectLfsFileContent(
                    "SIZE mandatory field parsing failed in Lfs file content".to_string(),
                )
            })?;
        Ok((version, oid, size))
    }

    pub fn get_lfs_struct(contents: &HashMap<&[u8], &[u8]>) -> Result<LFSContent> {
        Self::parse_mandatory_lfs(contents)
            .and_then(|(version, oid, size)| {
                Self::get_copied_lfs(contents).map(move |copy_from| (version, oid, size, copy_from))
            })
            .map(|(version, oid, size, copy_from)| LFSContent {
                version,
                oid,
                size,
                copy_from,
            })
    }

    fn get_copied_lfs(contents: &HashMap<&[u8], &[u8]>) -> Result<Option<(MPath, HgFileNodeId)>> {
        Self::get_copied_from_with_keys(contents, HGCOPY, HGCOPYREV)
    }

    pub fn generate_lfs_file(
        oid: Sha256,
        size: u64,
        copy_from: Option<(MPath, HgFileNodeId)>,
    ) -> Result<Bytes> {
        let git_version = String::from_utf8(GIT_VERSION.to_vec())?;
        let lfs_content = LFSContent {
            version: git_version,
            oid,
            size,
            copy_from,
        };
        lfs_content.into_bytes()
    }
}

const VERSION: &[u8] = b"version";
const OID: &[u8] = b"oid";
const SIZE: &[u8] = b"size";
const HGCOPY: &[u8] = b"x-hg-copy";
const HGCOPYREV: &[u8] = b"x-hg-copyrev";
const _ISBINARY: &[u8] = b"x-is-binary";
const GIT_VERSION: &[u8] = b"https://git-lfs.github.com/spec/v1";
const SHA256_PREFIX: &[u8] = b"sha256:";

// See [https://www.mercurial-scm.org/wiki/LfsPlan], By default, version, oid and size are required
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct LFSContent {
    // mandatory fields
    version: String,
    oid: Sha256,
    size: u64,

    // copy fields
    copy_from: Option<(MPath, HgFileNodeId)>,
}

impl LFSContent {
    pub fn new(
        version: String,
        oid: Sha256,
        size: u64,
        copy_from: Option<(MPath, HgFileNodeId)>,
    ) -> Self {
        Self {
            version,
            oid,
            size,
            copy_from,
        }
    }

    pub fn size(&self) -> u64 {
        self.size
    }

    pub fn oid(&self) -> Sha256 {
        self.oid.clone()
    }

    pub fn copy_from(&self) -> Option<(MPath, HgFileNodeId)> {
        self.copy_from.clone()
    }

    pub fn into_bytes(&self) -> Result<Bytes> {
        let mut out: Vec<u8> = vec![];

        out.write_all(VERSION)?;
        out.write_all(b" ")?;
        out.write_all(self.version.as_ref())?;
        out.write_all(b"\n")?;

        out.write_all(OID)?;
        out.write_all(b" ")?;
        out.write_all(SHA256_PREFIX)?;
        out.write_all(self.oid.to_hex().as_ref())?;
        out.write_all(b"\n")?;

        out.write_all(SIZE)?;
        out.write_all(b" ")?;
        out.write_all(format!("{}", self.size).as_ref())?;
        out.write_all(b"\n")?;

        if let Some((ref mpath, ref nodehash)) = self.copy_from {
            out.write_all(HGCOPY)?;
            out.write_all(b" ")?;
            mpath.generate(&mut out)?;
            out.write_all(b"\n")?;

            out.write_all(HGCOPYREV)?;
            out.write_all(b" ")?;
            out.write_all(nodehash.to_hex().as_ref())?;
            out.write_all(b"\n")?;
        }

        Ok(Bytes::from(out))
    }
}


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

// Utilities to lookup FilenodeIds and avoid recomputation.
//
// The motivation behind this is that given file contents, copy info, and parents,
// the file node ID is deterministic, but computing it requires fetching and
// hashing the body of the file. This implementation implements a lookup through
// the blobstore to find a pre-computed filenode ID.
//
// Doing so is in general a little inefficient (but it's not entirely certain that
// the implementation I'm proposing here is faster -- more on this below), but
// it's particularly problematic large files. Indeed, fetching a multiple-GB file
// to recompute the filenode even if we received it from the client can be fairly
// slow (and use up quite a bit of RAM, though that's something we can mitigate by
// streaming file contents).

use std::time::Duration;

use anyhow::Result;
use ascii::AsciiString;
use blobstore::Blobstore;
use bytes::Bytes;
use context::CoreContext;
use futures::Future;
use mononoke_types::BlobstoreBytes;
use mononoke_types::ContentId;
use mononoke_types::MPath;
use stats::prelude::*;
use tokio::time::timeout;
use tunables::tunables;

use crate::HgFileNodeId;

define_stats! {
    prefix = "mononoke.mercurial.filenode_lookup";
    timeout: timeseries(Rate, Sum),
}

#[derive(Debug, Eq, Hash, PartialEq)]
pub struct FileNodeIdPointer(String);

impl FileNodeIdPointer {
    pub fn new(
        content_id: &ContentId,
        copy_from: &Option<(MPath, HgFileNodeId)>,
        p1: &Option<HgFileNodeId>,
        p2: &Option<HgFileNodeId>,
    ) -> Self {
        let p1 = p1.as_ref().map(HgFileNodeId::to_hex).unwrap_or_default();

        let p2 = p2.as_ref().map(HgFileNodeId::to_hex).unwrap_or_default();

        let (copy_from_path, copy_from_filenode) =
            copy_from
                .as_ref()
                .map_or((AsciiString::new(), AsciiString::new()), |(mpath, fnid)| {
                    let path_hash = mpath.get_path_hash();
                    (path_hash.to_hex(), fnid.to_hex())
                });

        // Put all the deterministic parts together with a separator that cannot show up in them
        // (those are all hex digests or empty strings), then hash them. We'd like to use those
        // directly as our key, but that won't work in blobstores that limit the length of our keys
        // (e.g. SqlBlob).
        let mut ctx = mononoke_types::hash::Context::new("hgfilenode".as_bytes());

        ctx.update(content_id);
        ctx.update(b".");
        ctx.update(&p1);
        ctx.update(b".");
        ctx.update(&p2);
        ctx.update(b".");
        ctx.update(&copy_from_path);
        ctx.update(b".");
        ctx.update(&copy_from_filenode);

        let key = format!("filenode_lookup.{}", ctx.finish().to_hex());
        Self(key)
    }
    pub fn blobstore_key(&self) -> String {
        self.0.clone()
    }
}

pub fn store_filenode_id<'a, B: Blobstore>(
    ctx: &'a CoreContext,
    blobstore: &'a B,
    key: FileNodeIdPointer,
    filenode_id: &HgFileNodeId,
) -> impl Future<Output = Result<()>> + 'a {
    let contents = BlobstoreBytes::from_bytes(Bytes::copy_from_slice(filenode_id.as_bytes()));
    blobstore.put(ctx, key.0, contents)
}

pub async fn lookup_filenode_id<B: Blobstore>(
    ctx: &CoreContext,
    blobstore: &B,
    key: FileNodeIdPointer,
) -> Result<Option<HgFileNodeId>> {
    let filenode_lookup_timeout_ms = tunables().filenode_lookup_timeout_ms().unwrap_or_default();
    let fut = blobstore.get(ctx, &key.0);
    let blob = if filenode_lookup_timeout_ms > 0 {
        let maybe_timed_out = timeout(
            Duration::from_millis(filenode_lookup_timeout_ms as u64),
            fut,
        )
        .await;
        match maybe_timed_out {
            Ok(blob) => blob?,
            Err(_) => {
                STATS::timeout.add_value(1);
                return Ok(None);
            }
        }
    } else {
        fut.await?
    };

    Ok(blob.and_then(|blob| HgFileNodeId::from_bytes(blob.as_raw_bytes()).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.
 */

//! Root manifest, tree nodes

use std::str;

use anyhow::bail;
use anyhow::ensure;
use anyhow::Context;
use anyhow::Error;
use anyhow::Result;
use async_trait::async_trait;
use blobstore::Blobstore;
use blobstore::Loadable;
use blobstore::LoadableError;
use context::CoreContext;
use manifest::Entry;
use manifest::Manifest;
use sorted_vector_map::SortedVectorMap;

use super::errors::ErrorKind;
use crate::nodehash::HgNodeHash;
use crate::nodehash::NULL_HASH;
use crate::FileType;
use crate::HgBlob;
use crate::HgFileNodeId;
use crate::HgManifestEnvelope;
use crate::HgManifestId;
use crate::HgParents;
use crate::MPathElement;
use crate::Type;

#[derive(Debug, Eq, PartialEq)]
pub struct ManifestContent {
    pub files: SortedVectorMap<MPathElement, Entry<HgManifestId, (FileType, HgFileNodeId)>>,
}

impl ManifestContent {
    pub fn new_empty() -> Self {
        Self {
            files: SortedVectorMap::new(),
        }
    }

    // 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],
    ) -> Result<SortedVectorMap<MPathElement, Entry<HgManifestId, (FileType, HgFileNodeId)>>> {
        let lines = data.split(|b| *b == b'\n');
        let mut files = match lines.size_hint() {
            // Split returns it count in the high size hint
            (_, Some(high)) => SortedVectorMap::with_capacity(high),
            (_, None) => SortedVectorMap::new(),
        };

        for line in lines {
            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 = MPathElement::new(name.to_vec()).context("invalid path in manifest")?;
            let entry_id = parse_hg_entry(rest)?;

            files.insert(path, entry_id);
        }

        Ok(files)
    }

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

pub async fn fetch_raw_manifest_bytes<B: Blobstore>(
    ctx: &CoreContext,
    blobstore: &B,
    manifest_id: HgManifestId,
) -> Result<HgBlob> {
    let envelope = fetch_manifest_envelope(ctx, blobstore, manifest_id).await?;
    let envelope = envelope.into_mut();
    Ok(HgBlob::from(envelope.contents))
}

pub async fn fetch_manifest_envelope<B: Blobstore>(
    ctx: &CoreContext,
    blobstore: &B,
    manifest_id: HgManifestId,
) -> Result<HgManifestEnvelope> {
    let envelope = fetch_manifest_envelope_opt(ctx, blobstore, manifest_id).await?;
    Ok(envelope
        .ok_or_else(move || ErrorKind::HgContentMissing(manifest_id.into_nodehash(), Type::Tree))?)
}

/// Like `fetch_manifest_envelope`, but returns None if the manifest wasn't found.
pub async fn fetch_manifest_envelope_opt<B: Blobstore>(
    ctx: &CoreContext,
    blobstore: &B,
    node_id: HgManifestId,
) -> Result<Option<HgManifestEnvelope>> {
    let blobstore_key = node_id.blobstore_key();
    let bytes = blobstore
        .get(ctx, &blobstore_key)
        .await
        .context("While fetching manifest envelope blob")?;
    (|| {
        let blobstore_bytes = match bytes {
            Some(bytes) => bytes,
            None => return Ok(None),
        };
        let envelope = HgManifestEnvelope::from_blob(blobstore_bytes.into())?;
        if node_id.into_nodehash() != envelope.node_id() {
            bail!(
                "Manifest ID mismatch (requested: {}, got: {})",
                node_id,
                envelope.node_id()
            );
        }
        Ok(Some(envelope))
    })()
    .context(ErrorKind::ManifestDeserializeFailed(blobstore_key))
}

#[derive(Debug, Eq, PartialEq)]
pub struct HgBlobManifest {
    node_id: HgNodeHash,
    p1: Option<HgNodeHash>,
    p2: Option<HgNodeHash>,
    // See the documentation in mercurial_types/if/mercurial.thrift for why this exists.
    computed_node_id: HgNodeHash,
    content: ManifestContent,
}

impl HgBlobManifest {
    pub async fn load<B: Blobstore>(
        ctx: &CoreContext,
        blobstore: &B,
        manifestid: HgManifestId,
    ) -> Result<Option<Self>> {
        if manifestid.clone().into_nodehash() == NULL_HASH {
            Ok(Some(HgBlobManifest {
                node_id: NULL_HASH,
                p1: None,
                p2: None,
                computed_node_id: NULL_HASH,
                content: ManifestContent::new_empty(),
            }))
        } else {
            async {
                let envelope = fetch_manifest_envelope_opt(ctx, blobstore, manifestid).await?;
                match envelope {
                    Some(envelope) => Ok(Some(Self::parse(envelope)?)),
                    None => Result::<_>::Ok(None),
                }
            }
            .await
            .context(format!(
                "When loading manifest {} from blobstore",
                manifestid
            ))
        }
    }

    pub fn parse(envelope: HgManifestEnvelope) -> Result<Self> {
        let envelope = envelope.into_mut();
        let content = ManifestContent::parse(envelope.contents.as_ref()).with_context(|| {
            format!(
                "while parsing contents for manifest ID {}",
                envelope.node_id
            )
        })?;
        Ok(HgBlobManifest {
            node_id: envelope.node_id,
            p1: envelope.p1,
            p2: envelope.p2,
            computed_node_id: envelope.computed_node_id,
            content,
        })
    }

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

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

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

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

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

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

#[async_trait]
impl Loadable for HgManifestId {
    type Value = HgBlobManifest;

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

impl Manifest for HgBlobManifest {
    type TreeId = HgManifestId;
    type LeafId = (FileType, HgFileNodeId);

    fn lookup(&self, name: &MPathElement) -> Option<Entry<Self::TreeId, Self::LeafId>> {
        self.content.files.get(name).copied()
    }

    fn list(&self) -> Box<dyn Iterator<Item = (MPathElement, Entry<Self::TreeId, Self::LeafId>)>> {
        let iter = self.content.files.clone().into_iter();
        Box::new(iter)
    }
}

fn parse_hg_entry(data: &[u8]) -> Result<Entry<HgManifestId, (FileType, HgFileNodeId)>> {
    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 hg_entry_id = if flags.is_empty() {
        Entry::Leaf((FileType::Regular, HgFileNodeId::new(hash)))
    } else {
        match flags[0] {
            b'l' => Entry::Leaf((FileType::Symlink, HgFileNodeId::new(hash))),
            b'x' => Entry::Leaf((FileType::Executable, HgFileNodeId::new(hash))),
            b't' => Entry::Tree(HgManifestId::new(hash)),
            unk => bail!("Unknown flag {}", unk),
        }
    };

    Ok(hg_entry_id)
}

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


/*
 * 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 envelope;
pub use envelope::HgBlobEnvelope;

mod errors;
pub use errors::ErrorKind;

pub mod file;
pub use file::File;
pub use file::LFSContent;
pub use file::META_MARKER;
pub use file::META_SZ;

mod manifest;
pub use self::manifest::fetch_manifest_envelope;
pub use self::manifest::fetch_manifest_envelope_opt;
pub use self::manifest::fetch_raw_manifest_bytes;
pub use self::manifest::HgBlobManifest;
pub use self::manifest::ManifestContent;

mod changeset;
pub use changeset::serialize_cs;
pub use changeset::serialize_extras;
pub use changeset::ChangesetMetadata;
pub use changeset::Extra;
pub use changeset::HgBlobChangeset;
pub use changeset::HgChangesetContent;
pub use changeset::RevlogChangeset;

pub mod filenode_lookup;

mod upload;
pub use upload::ContentBlobMeta;
pub use upload::UploadHgFileContents;
pub use upload::UploadHgFileEntry;
pub use upload::UploadHgNodeHash;
pub use upload::UploadHgTreeEntry;


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

use std::sync::Arc;

use ::manifest::Entry;
use anyhow::bail;
use anyhow::Context;
use anyhow::Error;
use anyhow::Result;
use blobstore::Blobstore;
use bytes::Bytes;
use cloned::cloned;
use context::CoreContext;
use failure_ext::FutureErrorContext;
use filestore::FetchKey;
use filestore::FilestoreConfig;
use futures::compat::Future01CompatExt;
use futures::future;
use futures::future::Future;
use futures::future::FutureExt;
use futures::future::TryFutureExt;
use futures::pin_mut;
use futures::stream::StreamExt;
use futures::stream::TryStreamExt;
use futures_ext::BoxFuture;
use futures_ext::FutureExt as _;
use futures_old::future as future_old;
use futures_old::stream;
use futures_old::Future as FutureOld;
use futures_old::IntoFuture;
use futures_old::Stream;
use futures_stats::FutureStats;
use futures_stats::TimedFutureExt;
use futures_stats::TimedTryFutureExt;
use mononoke_types::ContentId;
use mononoke_types::MPath;
use mononoke_types::RepoPath;
use slog::trace;
use slog::Logger;
use stats::prelude::*;
use time_ext::DurationExt;

use super::errors::ErrorKind;
use super::filenode_lookup::lookup_filenode_id;
use super::filenode_lookup::store_filenode_id;
use super::filenode_lookup::FileNodeIdPointer;
use super::File;
use super::META_SZ;
use crate::calculate_hg_node_id_stream;
use crate::FileBytes;
use crate::HgBlobNode;
use crate::HgFileEnvelopeMut;
use crate::HgFileNodeId;
use crate::HgManifestEnvelopeMut;
use crate::HgManifestId;
use crate::HgNodeHash;
use crate::HgParents;

define_stats! {
    prefix = "mononoke.blobrepo";
    upload_hg_file_entry: timeseries(Rate, Sum),
    upload_hg_tree_entry: timeseries(Rate, Sum),
    upload_blob: timeseries(Rate, Sum),
}

/// Metadata associated with a content blob being uploaded as part of changeset creation.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ContentBlobMeta {
    pub id: ContentId,
    pub size: u64,
    // The copy info will later be stored as part of the commit.
    pub copy_from: Option<(MPath, HgFileNodeId)>,
}

/// Node hash handling for upload entries
pub enum UploadHgNodeHash {
    /// Generate the hash from the uploaded content
    Generate,
    /// This hash is used as the blobstore key, even if it doesn't match the hash of the
    /// parents and raw content. This is done because in some cases like root tree manifests
    /// in hybrid mode, Mercurial sends fake hashes.
    Supplied(HgNodeHash),
    /// As Supplied, but Verify the supplied hash - if it's wrong, you will get an error.
    Checked(HgNodeHash),
}

/// Context for uploading a Mercurial manifest entry.
pub struct UploadHgTreeEntry {
    pub upload_node_id: UploadHgNodeHash,
    pub contents: Bytes,
    pub p1: Option<HgNodeHash>, // TODO: How hard is it to udpate those?
    pub p2: Option<HgNodeHash>,
    pub path: RepoPath,
}

impl UploadHgTreeEntry {
    // Given the content of a manifest, ensure that there is a matching Entry in the repo.
    // This may not upload the entry or the data blob if the repo is aware of that data already
    // existing in the underlying store.
    //
    // Note that the Entry may not be consistent - parents do not have to be uploaded at this
    // point, as long as you know their HgNodeHashes; this is also given to you as part of the
    // result type, so that you can parallelise uploads. Consistency will be verified when adding
    // the entries to a changeset.
    pub fn upload(
        self,
        ctx: CoreContext,
        blobstore: Arc<dyn Blobstore>,
    ) -> Result<(HgManifestId, BoxFuture<(HgManifestId, RepoPath), Error>)> {
        STATS::upload_hg_tree_entry.add_value(1);
        let UploadHgTreeEntry {
            upload_node_id,
            contents,
            p1,
            p2,
            path,
        } = self;

        let logger = ctx.logger().clone();
        let computed_node_id = HgBlobNode::new(contents.clone(), p1, p2).nodeid();
        let node_id: HgNodeHash = match upload_node_id {
            UploadHgNodeHash::Generate => computed_node_id,
            UploadHgNodeHash::Supplied(node_id) => node_id,
            UploadHgNodeHash::Checked(node_id) => {
                if node_id != computed_node_id {
                    bail!(ErrorKind::InconsistentEntryHashForPath(
                        path,
                        node_id,
                        computed_node_id
                    ));
                }
                node_id
            }
        };

        // This is the blob that gets uploaded. Manifest contents are usually small so they're
        // stored inline.
        let envelope = HgManifestEnvelopeMut {
            node_id,
            p1,
            p2,
            computed_node_id,
            contents,
        };
        let envelope_blob = envelope.freeze().into_blob();

        let manifest_id = HgManifestId::new(node_id);
        let blobstore_key = manifest_id.blobstore_key();

        fn log_upload_stats(
            logger: Logger,
            path: RepoPath,
            node_id: HgNodeHash,
            computed_node_id: HgNodeHash,
            stats: FutureStats,
        ) {
            trace!(logger, "Upload HgManifestEnvelope stats";
                "phase" => "manifest_envelope_uploaded".to_string(),
                "path" => format!("{}", path),
                "node_id" => format!("{}", node_id),
                "computed_node_id" => format!("{}", computed_node_id),
                "poll_count" => stats.poll_count,
                "poll_time_us" => stats.poll_time.as_micros_unchecked(),
                "completion_time_us" => stats.completion_time.as_micros_unchecked(),
            );
        }

        // Upload the blob.
        let upload = async move {
            blobstore
                .put(&ctx, blobstore_key, envelope_blob.into())
                .await
        }
        .map_ok({
            let path = path.clone();
            move |()| (manifest_id, path)
        })
        .timed()
        .map({
            let logger = logger.clone();
            move |(stats, result)| {
                if result.is_ok() {
                    log_upload_stats(logger, path, node_id, computed_node_id, stats);
                }
                result
            }
        })
        .boxed()
        .compat();

        Ok((manifest_id, upload.boxify()))
    }

    pub fn upload_as_entry(
        self,
        ctx: CoreContext,
        blobstore: Arc<dyn Blobstore>,
    ) -> Result<(
        HgManifestId,
        BoxFuture<(Entry<HgManifestId, HgFileNodeId>, RepoPath), Error>,
    )> {
        self.upload(ctx, blobstore.clone()).map({
            move |(mfid, fut)| {
                (
                    mfid,
                    fut.map(move |(mfid, repo_path)| (Entry::Tree(mfid), repo_path))
                        .boxify(),
                )
            }
        })
    }
}

/// What sort of file contents are available to upload.
pub enum UploadHgFileContents {
    /// Content already uploaded (or scheduled to be uploaded). Metadata will be inlined in
    /// the envelope.
    ContentUploaded(ContentBlobMeta),
    /// Raw bytes as would be sent by Mercurial, including any metadata prepended in the standard
    /// Mercurial format.
    RawBytes(Bytes, FilestoreConfig),
}

impl UploadHgFileContents {
    /// Upload the file contents if necessary, and asynchronously return the hash of the file node
    /// and metadata.
    fn execute(
        self,
        ctx: CoreContext,
        blobstore: &Arc<dyn Blobstore>,
        p1: Option<HgFileNodeId>,
        p2: Option<HgFileNodeId>,
    ) -> (
        ContentBlobMeta,
        // The future that does the upload and the future that computes the node ID/metadata are
        // split up to allow greater parallelism.
        impl FutureOld<Item = (), Error = Error> + Send,
        impl FutureOld<Item = (HgFileNodeId, Bytes, u64), Error = Error> + Send,
    ) {
        let (cbmeta, upload_fut, compute_fut) = match self {
            UploadHgFileContents::ContentUploaded(cbmeta) => {
                let upload_fut = future_old::ok(());

                let size = cbmeta.size;

                let lookup_fut = {
                    cloned!(ctx, blobstore);
                    let file_node_id_ptr =
                        FileNodeIdPointer::new(&cbmeta.id, &cbmeta.copy_from, &p1, &p2);
                    async move {
                        lookup_filenode_id(&ctx, &blobstore, file_node_id_ptr)
                            .await
                            .context("failed to lookup filenode id")
                    }
                };

                let metadata_fut = Self::compute_metadata(
                    ctx.clone(),
                    blobstore,
                    cbmeta.id,
                    cbmeta.copy_from.clone(),
                );

                let content_id = cbmeta.id;

                // Attempt to lookup filenode ID by alias. Fallback to computing it if we cannot.
                let compute_fut = future::try_join(lookup_fut, metadata_fut)
                    .boxed()
                    .compat()
                    .and_then({
                        cloned!(ctx, blobstore);
                        move |(res, metadata)| {
                            res.ok_or(())
                                .into_future()
                                .or_else({
                                    cloned!(metadata);
                                    move |_| {
                                        Self::compute_filenode_id(
                                            ctx, &blobstore, content_id, metadata, p1, p2,
                                        )
                                    }
                                })
                                .map(move |fnid| (fnid, metadata, size))
                        }
                    });

                (cbmeta, upload_fut.left_future(), compute_fut.left_future())
            }
            UploadHgFileContents::RawBytes(raw_content, filestore_config) => {
                let node_id = HgFileNodeId::new(
                    HgBlobNode::new(
                        raw_content.clone(),
                        p1.map(HgFileNodeId::into_nodehash),
                        p2.map(HgFileNodeId::into_nodehash),
                    )
                    .nodeid(),
                );

                let f = File::new(raw_content, p1, p2);
                let metadata = f.metadata();

                let copy_from = match f.copied_from() {
                    Ok(copy_from) => copy_from,
                    // XXX error out if copy-from information couldn't be read?
                    Err(_err) => None,
                };
                // Upload the contents separately (they'll be used for bonsai changesets as well).
                let file_bytes = f.file_contents();

                STATS::upload_blob.add_value(1);
                let ((id, size), upload_fut) = filestore::store_bytes(
                    blobstore,
                    filestore_config,
                    &ctx,
                    file_bytes.into_bytes(),
                );

                let upload_fut = upload_fut
                    .timed()
                    .map({
                        let logger = ctx.logger().clone();
                        move |(stats, result)| {
                            if result.is_ok() {
                                UploadHgFileEntry::log_stats(
                                    logger,
                                    None,
                                    node_id,
                                    "content_uploaded",
                                    stats,
                                );
                            }
                            result
                        }
                    })
                    .boxed()
                    .compat();

                let cbmeta = ContentBlobMeta {
                    id,
                    size,
                    copy_from,
                };

                let compute_fut = future_old::ok((node_id, metadata, size));

                (
                    cbmeta,
                    upload_fut.right_future(),
                    compute_fut.right_future(),
                )
            }
        };

        let key = FileNodeIdPointer::new(&cbmeta.id, &cbmeta.copy_from, &p1, &p2);

        let compute_fut = compute_fut.and_then({
            cloned!(ctx, blobstore);
            move |(filenode_id, metadata, size)| {
                async move { store_filenode_id(&ctx, &blobstore, key, &filenode_id).await }
                    .map_ok(move |()| (filenode_id, metadata, size))
                    .boxed()
                    .compat()
            }
        });

        (cbmeta, upload_fut, compute_fut)
    }

    fn compute_metadata(
        ctx: CoreContext,
        blobstore: &Arc<dyn Blobstore>,
        content_id: ContentId,
        copy_from: Option<(MPath, HgFileNodeId)>,
    ) -> impl Future<Output = Result<Bytes, Error>> {
        cloned!(blobstore);

        async move {
            let bytes = async {
                Result::<_>::Ok(
                    filestore::peek(&blobstore, &ctx, &FetchKey::Canonical(content_id), META_SZ)
                        .await?
                        .ok_or(ErrorKind::ContentBlobMissing(content_id))?,
                )
            }
            .await
            .context("While computing metadata")?;
            let mut metadata = Vec::new();
            File::generate_metadata(copy_from.as_ref(), &FileBytes(bytes), &mut metadata)
                .expect("Vec::write_all should never fail");

            // TODO: Introduce Metadata bytes?
            Ok(Bytes::from(metadata))
        }
    }

    fn compute_filenode_id(
        ctx: CoreContext,
        blobstore: &Arc<dyn Blobstore>,
        content_id: ContentId,
        metadata: Bytes,
        p1: Option<HgFileNodeId>,
        p2: Option<HgFileNodeId>,
    ) -> impl FutureOld<Item = HgFileNodeId, Error = Error> {
        cloned!(blobstore);

        let file_bytes = async_stream::stream! {
            let stream = filestore::fetch(&blobstore, ctx, &FetchKey::Canonical(content_id))
                .await?
                .ok_or(ErrorKind::ContentBlobMissing(content_id))?;

            pin_mut!(stream);
            while let Some(value) = stream.next().await {
                yield value;
            }
        }
        .boxed()
        .compat();

        let all_bytes = stream::once(Ok(metadata)).chain(file_bytes);

        let hg_parents = HgParents::new(
            p1.map(HgFileNodeId::into_nodehash),
            p2.map(HgFileNodeId::into_nodehash),
        );

        calculate_hg_node_id_stream(all_bytes, &hg_parents)
            .map(HgFileNodeId::new)
            .context("While computing a filenode id")
            .from_err()
    }
}

/// Context for uploading a Mercurial file entry.
pub struct UploadHgFileEntry {
    pub upload_node_id: UploadHgNodeHash,
    pub contents: UploadHgFileContents,
    pub p1: Option<HgFileNodeId>,
    pub p2: Option<HgFileNodeId>,
}

impl UploadHgFileEntry {
    pub async fn upload(
        self,
        ctx: CoreContext,
        blobstore: Arc<dyn Blobstore>,
        path: Option<&MPath>, // This is used for logging
    ) -> Result<HgFileNodeId, Error> {
        STATS::upload_hg_file_entry.add_value(1);
        let UploadHgFileEntry {
            upload_node_id,
            contents,
            p1,
            p2,
        } = self;

        let (cbmeta, content_upload, compute_fut) =
            contents.execute(ctx.clone(), &blobstore, p1, p2);
        let content_id = cbmeta.id;
        let logger = ctx.logger().clone();

        let envelope_upload = async move {
            let (computed_node_id, metadata, content_size) = compute_fut.compat().await?;

            let node_id = match upload_node_id {
                UploadHgNodeHash::Generate => computed_node_id,
                UploadHgNodeHash::Supplied(node_id) => HgFileNodeId::new(node_id),
                UploadHgNodeHash::Checked(node_id) => {
                    let node_id = HgFileNodeId::new(node_id);
                    if node_id != computed_node_id {
                        let err = match path {
                            Some(path) => ErrorKind::InconsistentEntryHashForPath(
                                RepoPath::FilePath((*path).clone()),
                                node_id.into_nodehash(),
                                computed_node_id.into_nodehash(),
                            ),
                            None => ErrorKind::InconsistentEntryHash(
                                node_id.into_nodehash(),
                                computed_node_id.into_nodehash(),
                            ),
                        };
                        return Err(Error::from(err));
                    }
                    node_id
                }
            };

            let file_envelope = HgFileEnvelopeMut {
                node_id,
                p1,
                p2,
                content_id,
                content_size,
                metadata,
            };
            let envelope_blob = file_envelope.freeze().into_blob();

            let blobstore_key = node_id.blobstore_key();

            let (stats, ()) = blobstore
                .put(&ctx, blobstore_key, envelope_blob.into())
                .try_timed()
                .await?;

            Self::log_stats(logger, path, node_id, "file_envelope_uploaded", stats);

            Ok(node_id)
        };

        let (ret, ()) = future::try_join(envelope_upload, content_upload.compat()).await?;

        Ok(ret)
    }

    /// Upload and provide a path for this upload. This will be returned to the caller, but wil
    /// also be used for logging.
    pub async fn upload_with_path(
        self,
        ctx: CoreContext,
        blobstore: Arc<dyn Blobstore>,
        path: MPath,
    ) -> Result<(HgFileNodeId, RepoPath), Error> {
        let filenode_id = self.upload(ctx, blobstore.clone(), Some(&path)).await?;
        Ok((filenode_id, RepoPath::FilePath(path)))
    }

    pub async fn upload_as_entry(
        self,
        ctx: CoreContext,
        blobstore: Arc<dyn Blobstore>,
        path: MPath,
    ) -> Result<(Entry<HgManifestId, HgFileNodeId>, RepoPath), Error> {
        let filenode_id = self.upload(ctx, blobstore.clone(), Some(&path)).await?;
        Ok((Entry::Leaf(filenode_id), RepoPath::FilePath(path)))
    }

    fn log_stats(
        logger: Logger,
        path: Option<&MPath>,
        nodeid: HgFileNodeId,
        phase: &str,
        stats: FutureStats,
    ) {
        let path = path.map_or_else(String::new, |p| p.to_string());
        let nodeid = format!("{}", nodeid);
        trace!(logger, "Upload blob stats";
            "phase" => String::from(phase),
            "path" => path,
            "nodeid" => nodeid,
            "poll_count" => stats.poll_count,
            "poll_time_us" => stats.poll_time.as_micros_unchecked(),
            "completion_time_us" => stats.completion_time.as_micros_unchecked(),
        );
    }
}