330 lines · 12.5 KB · Rust
RawDownload
/// Convert a hexadecimal hash into its corresponding `ObjectId` or _panic_.fn hex_to_id(hex_sha1: &str, hex_sha256: &str) -> gix_hash::ObjectId {    match gix_testtools::object_hash() {        gix_hash::Kind::Sha1 => gix_hash::ObjectId::from_hex(hex_sha1.as_bytes()).expect("40 bytes hex"),        gix_hash::Kind::Sha256 => gix_hash::ObjectId::from_hex(hex_sha256.as_bytes()).expect("64 bytes hex"),        _ => unimplemented!(),    }} mod from_tree {    use std::{        convert::Infallible,        io::{Error, Read, Write},        path::PathBuf,        sync::Arc,    };     use gix_attributes::glob::pattern::Case;    use gix_hash::oid;    use gix_object::{Data, bstr::ByteSlice, tree::EntryKind};    use gix_worktree::stack::state::attributes::Source;    use std::sync::LazyLock;     use crate::hex_to_id;     #[derive(Clone)]    struct FailObjectRetrieval;     impl gix_object::Find for FailObjectRetrieval {        fn try_find<'a>(            &self,            _id: &oid,            _buffer: &'a mut Vec<u8>,        ) -> Result<Option<Data<'a>>, gix_object::find::Error> {            Err(Box::new(Error::other("object retrieval failed")))        }    }     #[test]    fn can_receive_err_if_root_is_not_found() {        let mut stream = gix_worktree_stream::from_tree(            gix_testtools::object_hash().null(),            FailObjectRetrieval,            mutating_pipeline(false),            |_, _, _| -> Result<_, Infallible> { unreachable!("must not be called") },        );        let err = stream.next_entry().unwrap_err();        assert_eq!(err.to_string(), "Could not find a tree to traverse");    }     #[test]    fn can_receive_err_if_attribute_not_found() -> gix_testtools::Result {        let (_dir, head_tree, odb, _cache) = basic()?;        let mut stream = gix_worktree_stream::from_tree(head_tree, odb, mutating_pipeline(false), |_, _, _| {            Err(Error::other("attribute retrieval failed"))        });        let err = stream.next_entry().unwrap_err();        assert_eq!(            err.to_string(),            "Could not query attributes for path \".gitattributes\""        );        Ok(())    }     #[test]    fn will_provide_all_information_and_respect_export_ignore() -> gix_testtools::Result {        let (dir, head_tree, odb, mut cache) = basic()?;        let mut stream = gix_worktree_stream::from_tree(            head_tree,            odb.clone(),            mutating_pipeline(true),            move |rela_path, mode, attrs| {                cache                    .at_entry(rela_path, Some(mode.into()), &odb)                    .map(|entry| entry.matching_attributes(attrs))                    .map(|_| ())            },        );        let object_hash = gix_testtools::object_hash();        stream            .add_entry_from_path(&dir, &dir.join("extra-file"), object_hash)?            .add_entry_from_path(&dir, &dir.join("extra-bigfile"), object_hash)?            .add_entry_from_path(&dir, &dir.join("extra-exe"), object_hash)?            .add_entry_from_path(&dir, &dir.join("extra-dir-empty"), object_hash)?            .add_entry_from_path(&dir, &dir.join("extra-dir").join("symlink-to-extra"), object_hash)?;         let tee_read = TeeToMemory {            read: stream.into_read(),            write: Default::default(),        };        let copy = tee_read.write.clone();        let mut paths_and_modes = Vec::new();        let mut stream = gix_worktree_stream::Stream::from_read(tee_read);         while let Some(mut entry) = stream.next_entry().expect("entry retrieval does not fail") {            paths_and_modes.push((entry.relative_path().to_owned(), entry.mode.kind(), entry.id));            let mut buf = Vec::new();            entry.read_to_end(&mut buf).expect("stream can always be read");            if !buf.is_empty() && entry.mode.is_blob() {                if entry.relative_path().contains_str("extra") {                    assert!(                        buf.find_byte(b'\r').is_none(),                        "extra-files are not processed in any way"                    );                } else if !entry.relative_path().contains_str("big") {                    assert!(                        buf.find_byte(b'\r').is_some(),                        "'{}' did not contain a carriage return as sign of having been filtered",                        buf.as_bstr()                    );                    if entry.relative_path().ends_with_str(b"streamed") {                        assert_eq!(buf.as_bstr(), "➡streamed-by-driver\r\n");                    }                }            }        }         assert_eq!(            paths_and_modes,            &[                (                    ".gitattributes".into(),                    EntryKind::Blob,                    hex_to_id(                        "45c160c35c17ad264b96431cceb9793160396e99",                        "e34ae8e2c517230c6c613202a955b5f2095567830de5c3bb7081d72f1af393dd"                    )                ),                (                    "a".into(),                    EntryKind::Blob,                    hex_to_id(                        "45b983be36b73c0788dc9cbcb76cbb80fc7bb057",                        "96c18f0297e38d01f4b2dacddea4259aea6b2961eb0822bd2c0c3f6029030045"                    )                ),                (                    "bigfile".into(),                    EntryKind::Blob,                    hex_to_id(                        "4995fde49ed64e043977e22539f66a0d372dd129",                        "527a170c0c29520af58bf61a383c4e99a7a7c63c0475cbb630cf98d38d6c0dce"                    )                ),                (                    "symlink-to-a".into(),                    EntryKind::Link,                    hex_to_id(                        "2e65efe2a145dda7ee51d1741299f848e5bf752e",                        "eb337bcee2061c5313c9a1392116b6c76039e9e30d71467ae359b36277e17dc7"                    )                ),                (                    "dir/.gitattributes".into(),                    EntryKind::Blob,                    hex_to_id(                        "81b9a375276405703e05be6cecf0fc1c8b8eed64",                        "4b74474eca46ad7e1807afe8855e7a511d132b314d3d0f481bb55a331b249fb7"                    )                ),                (                    "dir/b".into(),                    EntryKind::Blob,                    hex_to_id(                        "ab4a98190cf776b43cb0fe57cef231fb93fd07e6",                        "cf84f9ae227fa5c481dacd97f6d6506de727dabd30377c6907f623ef1192637a"                    )                ),                (                    "dir/subdir/exe".into(),                    EntryKind::BlobExecutable,                    hex_to_id(                        "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",                        "473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813"                    )                ),                (                    "dir/subdir/streamed".into(),                    EntryKind::Blob,                    hex_to_id(                        "08991f58f4de5d85b61c0f87f3ac053c79d0e739",                        "8c13ad7df9686daf00357f34700922485802cb0be33e684ec5171f7d0d8a84fd"                    )                ),                ("extra-file".into(), EntryKind::Blob, object_hash.null()),                ("extra-bigfile".into(), EntryKind::Blob, object_hash.null()),                (                    "extra-exe".into(),                    if cfg!(windows) {                        EntryKind::Blob                    } else {                        EntryKind::BlobExecutable                    },                    object_hash.null()                ),                ("extra-dir-empty".into(), EntryKind::Tree, object_hash.null()),                ("extra-dir/symlink-to-extra".into(), EntryKind::Link, object_hash.null())            ]        );         #[cfg(target_pointer_width = "64")]        let expected_buffer_length: usize = match gix_testtools::object_hash() {            gix_hash::Kind::Sha1 => 320302,            gix_hash::Kind::Sha256 => 320458,            _ => unimplemented!(),        };        #[cfg(target_pointer_width = "32")]        let expected_buffer_length: usize = match gix_testtools::object_hash() {            gix_hash::Kind::Sha1 => 320198,            gix_hash::Kind::Sha256 => todo!("let the test fail on CI and add the value here"),            _ => unimplemented!(),        };         assert_eq!(            copy.lock().len(),            expected_buffer_length,            "keep track of file size changes of the streaming format"        );         let mut copied_stream =            gix_worktree_stream::Stream::from_read(std::io::Cursor::new(copy.lock().as_bytes().to_owned()));        let mut copied_paths_and_modes = Vec::new();        let mut buf = Vec::new();        while let Some(mut entry) = copied_stream.next_entry().expect("entry retrieval does not fail") {            copied_paths_and_modes.push((entry.relative_path().to_owned(), entry.mode.kind(), entry.id));            buf.clear();            entry.read_to_end(&mut buf).expect("stream can always be read");        }        assert_eq!(            copied_paths_and_modes, paths_and_modes,            "a stream copy yields exactly the same result"        );        Ok(())    }     #[test]    fn can_drop_entry_without_reading_it() -> gix_testtools::Result {        let (_dir, head_tree, odb, mut cache) = basic()?;        let mut stream = gix_worktree_stream::from_tree(            head_tree,            odb.clone(),            mutating_pipeline(false),            move |rela_path, mode, attrs| {                cache                    .at_entry(rela_path, Some(mode.into()), &odb)                    .map(|entry| entry.matching_attributes(attrs))                    .map(|_| ())            },        );         drop(stream.next_entry().expect("entry retrieval does not fail"));        Ok(())    }     fn basic() -> gix_testtools::Result<(PathBuf, gix_hash::ObjectId, gix_odb::HandleArc, gix_worktree::Stack)> {        let dir = gix_testtools::scripted_fixture_read_only("basic.sh")?;         let head = {            let hex = std::fs::read(dir.join("head.hex"))?;            gix_hash::ObjectId::from_hex(hex.trim())?        };        let odb = gix_odb::at_opts(            dir.join(".git").join("objects"),            Vec::new(),            gix_odb::store::init::Options {                object_hash: gix_testtools::object_hash(),                ..Default::default()            },        )?;         let mut collection = Default::default();        let mut buf = Default::default();        let attributes = gix_worktree::stack::state::Attributes::new(            gix_attributes::Search::new_globals(None::<PathBuf>, &mut buf, &mut collection)?,            None,            Source::WorktreeThenIdMapping,            collection,        );        let state = gix_worktree::stack::State::AttributesStack(attributes);        let cache = gix_worktree::Stack::new(&dir, state, Case::Sensitive, Default::default(), Default::default());        Ok((dir, head, odb.into_arc()?, cache))    }     fn mutating_pipeline(driver: bool) -> gix_filter::Pipeline {        gix_filter::Pipeline::new(            Default::default(),            gix_filter::pipeline::Options {                drivers: if driver { vec![driver_with_process()] } else { vec![] },                eol_config: gix_filter::eol::Configuration {                    auto_crlf: gix_filter::eol::AutoCrlf::Enabled,                    ..Default::default()                },                ..Default::default()            },        )    }     pub(crate) fn driver_with_process() -> gix_filter::Driver {        let mut exe = DRIVER.to_string_lossy().into_owned();        if cfg!(windows) {            exe = exe.replace('\\', "/");        }        gix_filter::Driver {            name: "arrow".into(),            clean: None,            smudge: None,            process: Some((exe + " process").into()),            required: true,        }    }     static DRIVER: LazyLock<PathBuf> =        LazyLock::new(|| gix_testtools::build_example_for_test("gix-filter", "arrow", env!("CARGO_TARGET_TMPDIR")));     struct TeeToMemory<R> {        read: R,        write: Arc<parking_lot::Mutex<Vec<u8>>>,    }     impl<R> std::io::Read for TeeToMemory<R>    where        R: std::io::Read,    {        fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {            let nb = self.read.read(buf)?;            self.write.lock().write_all(&buf[..nb])?;            Ok(nb)        }    }}