626 lines · 23.8 KB · Rust
RawDownload
use crate::{remote, util::restricted}; #[cfg(feature = "blocking-network-client")]mod blocking_io {    use std::sync::atomic::AtomicBool;     use gix::{        bstr::BString,        config::tree::{Clone, Core, Init, Key},        remote::{            fetch::{Shallow, SpecIndex},            Direction,        },    };    use gix_object::bstr::ByteSlice;    use gix_ref::TargetRef;     use crate::{        remote,        util::{hex_to_id, restricted},    };     #[test]    fn fetch_shallow_no_checkout_then_unshallow() -> crate::Result {        let tmp = gix_testtools::tempfile::TempDir::new()?;        let called_configure_remote = std::sync::Arc::new(std::sync::atomic::AtomicBool::default());        let remote_name = "special";        let desired_fetch_tags = gix::remote::fetch::Tags::Included;        let mut prepare = gix::prepare_clone_bare(remote::repo("base").path(), tmp.path())?            .with_remote_name(remote_name)?            .configure_remote({                let called_configure_remote = called_configure_remote;                move |r| {                    called_configure_remote.store(true, std::sync::atomic::Ordering::Relaxed);                    let mut r = r.with_fetch_tags(desired_fetch_tags);                    r.replace_refspecs(                        [                            BString::from(format!("refs/heads/main:refs/remotes/{remote_name}/main")),                            "+refs/tags/b-tag:refs/tags/b-tag".to_owned().into(),                        ],                        Direction::Fetch,                    )?;                    Ok(r)                }            })            .with_shallow(Shallow::DepthAtRemote(2.try_into().expect("non-zero")));        let (repo, _out) = prepare.fetch_only(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?;        drop(prepare);         assert_eq!(            repo.shallow_commits()?.expect("shallow").as_slice(),            [                hex_to_id("27e71576a6335294aa6073ab767f8b36bdba81d0"),                hex_to_id("2d9d136fb0765f2e24c44a0f91984318d580d03b"),                hex_to_id("82024b2ef7858273337471cbd1ca1cedbdfd5616"),                hex_to_id("b5152869aedeb21e55696bb81de71ea1bb880c85")            ],            "shallow information is written"        );         let shallow_commit_count = repo.head_id()?.ancestors().all()?.count();        let remote = repo.head()?.into_remote(Direction::Fetch).expect("present")?;         remote            .connect(Direction::Fetch)?            .prepare_fetch(gix::progress::Discard, Default::default())?            .with_shallow(Shallow::undo())            .receive(gix::progress::Discard, &AtomicBool::default())?;         assert!(repo.shallow_commits()?.is_none(), "the repo isn't shallow anymore");        assert!(            !repo.is_shallow(),            "both methods agree - if there are no shallow commits, it shouldn't think the repo is shallow"        );        assert!(            !repo.shallow_file().exists(),            "when the repo is not shallow anymore, there is no need for a shallow file"        );        assert!(            repo.head_id()?.ancestors().all()?.count() > shallow_commit_count,            "there are more commits now as the history is complete"        );         Ok(())    }     #[test]    fn from_shallow_prohibited_with_option() -> crate::Result {        let tmp = gix_testtools::tempfile::TempDir::new()?;        let err = gix::clone::PrepareFetch::new(            remote::repo("base.shallow").path(),            tmp.path(),            gix::create::Kind::Bare,            Default::default(),            gix::open::Options::isolated().config_overrides([Clone::REJECT_SHALLOW.validated_assignment_fmt(&true)?]),        )?        .fetch_only(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())        .unwrap_err();        assert!(            matches!(                err,                gix::clone::fetch::Error::Fetch(gix::remote::fetch::Error::RejectShallowRemote)            ),            "we can avoid fetching from remotes with this setting"        );        Ok(())    }     #[test]    fn from_shallow_allowed_by_default() -> crate::Result {        let tmp = gix_testtools::tempfile::TempDir::new()?;        let (repo, _change) = gix::prepare_clone_bare(remote::repo("base.shallow").path(), tmp.path())?            .fetch_only(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?;        assert_eq!(            repo.shallow_commits()?.expect("present").as_slice(),            vec![                hex_to_id("2d9d136fb0765f2e24c44a0f91984318d580d03b"),                hex_to_id("dfd0954dabef3b64f458321ef15571cc1a46d552"),                hex_to_id("dfd0954dabef3b64f458321ef15571cc1a46d552"),            ]        );        Ok(())    }     #[test]    fn from_non_shallow_then_deepen_then_deepen_since_to_unshallow() -> crate::Result {        let tmp = gix_testtools::tempfile::TempDir::new()?;        let (repo, _change) = gix::prepare_clone_bare(remote::repo("base").path(), tmp.path())?            .with_shallow(Shallow::DepthAtRemote(2.try_into()?))            .configure_remote(|mut r| {                r.replace_refspecs(Some("refs/heads/main:refs/remotes/origin/main"), Direction::Fetch)?;                Ok(r)            })            .fetch_only(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?;         assert!(repo.is_shallow());        assert_eq!(            repo.shallow_commits()?.expect("present").as_slice(),            vec![                hex_to_id("2d9d136fb0765f2e24c44a0f91984318d580d03b"),                hex_to_id("dfd0954dabef3b64f458321ef15571cc1a46d552"),            ]        );         let shallow_commit_count = repo.head_id()?.ancestors().all()?.count();         let remote = repo.head()?.into_remote(Direction::Fetch).expect("present")?;        remote            .connect(Direction::Fetch)?            .prepare_fetch(gix::progress::Discard, Default::default())?            .with_shallow(Shallow::Deepen(1))            .receive(gix::progress::Discard, &AtomicBool::default())?;         assert_eq!(            repo.shallow_commits()?.expect("present").as_slice(),            vec![                hex_to_id("27e71576a6335294aa6073ab767f8b36bdba81d0"),                hex_to_id("82024b2ef7858273337471cbd1ca1cedbdfd5616"),                hex_to_id("b5152869aedeb21e55696bb81de71ea1bb880c85"),            ],            "the shallow boundary was changed"        );        assert!(            repo.head_id()?.ancestors().all()?.count() > shallow_commit_count,            "there are more commits now as the history was deepened"        );         let shallow_commit_count = repo.head_id()?.ancestors().all()?.count();        remote            .connect(Direction::Fetch)?            .prepare_fetch(gix::progress::Discard, Default::default())?            .with_shallow(Shallow::Since {                cutoff: gix::date::Time::new(1112354053, 0),            })            .receive(gix::progress::Discard, &AtomicBool::default())?;         assert!(            !repo.is_shallow(),            "the cutoff date is before the first commit, effectively unshallowing"        );        assert!(            repo.head_id()?.ancestors().all()?.count() > shallow_commit_count,            "there is even more commits than previously"        );        Ok(())    }     #[test]    fn from_non_shallow_by_deepen_exclude_then_deepen_to_unshallow() -> crate::Result {        let tmp = gix_testtools::tempfile::TempDir::new()?;        let excluded_leaf_refs = ["g", "h", "j"];        let (repo, _change) = gix::prepare_clone_bare(remote::repo("base").path(), tmp.path())?            .with_shallow(Shallow::Exclude {                remote_refs: excluded_leaf_refs                    .into_iter()                    .map(|n| n.try_into().expect("valid"))                    .collect(),                since_cutoff: None,            })            .fetch_only(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?;         assert!(repo.is_shallow());        assert_eq!(            repo.shallow_commits()?.expect("present").as_slice(),            vec![                hex_to_id("27e71576a6335294aa6073ab767f8b36bdba81d0"),                hex_to_id("82024b2ef7858273337471cbd1ca1cedbdfd5616"),            ]        );         let remote = repo.head()?.into_remote(Direction::Fetch).expect("present")?;        remote            .connect(Direction::Fetch)?            .prepare_fetch(gix::progress::Discard, Default::default())?            .with_shallow(Shallow::Deepen(2))            .receive(gix::progress::Discard, &AtomicBool::default())?;         assert!(!repo.is_shallow(), "one is just enough to unshallow it");        Ok(())    }     #[test]    fn fetch_only_with_configuration() -> crate::Result {        let tmp = gix_testtools::tempfile::TempDir::new()?;        let called_configure_remote = std::sync::Arc::new(std::sync::atomic::AtomicBool::default());        let remote_name = "special";        let desired_fetch_tags = gix::remote::fetch::Tags::Included;        let mut prepare = gix::clone::PrepareFetch::new(            remote::repo("base").path(),            tmp.path(),            gix::create::Kind::Bare,            Default::default(),            gix::open::Options::isolated().config_overrides([                Init::DEFAULT_BRANCH.validated_assignment_fmt(&"unused-as-overridden-by-remote")?,                Core::LOG_ALL_REF_UPDATES.logical_name().into(),                // missing user and email is acceptable in this special case, i.e. `git` also doesn't mind filling it in.            ]),        )?        .with_remote_name(remote_name)?        .configure_remote({            let called_configure_remote = called_configure_remote.clone();            move |r| {                called_configure_remote.store(true, std::sync::atomic::Ordering::Relaxed);                let r = r                    .with_refspecs(Some("+refs/tags/b-tag:refs/tags/b-tag"), gix::remote::Direction::Fetch)?                    .with_fetch_tags(desired_fetch_tags);                Ok(r)            }        });        let (repo, out) = prepare.fetch_only(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?;        drop(prepare);         assert!(            called_configure_remote.load(std::sync::atomic::Ordering::Relaxed),            "custom remote configuration is called"        );        assert_eq!(repo.remote_names().len(), 1, "only ever one remote");        let remote = repo.find_remote(remote_name)?;        let num_refspecs = remote.refspecs(gix::remote::Direction::Fetch).len();        assert_eq!(            num_refspecs, 2,            "our added spec was stored as well, but no implied specs due to the `Tags::All` setting"        );        assert_eq!(            remote.fetch_tags(),            desired_fetch_tags,            "fetch-tags are persisted via the 'tagOpt` key"        );        assert!(            gix::path::from_bstr(                remote                    .url(gix::remote::Direction::Fetch)                    .expect("present")                    .path                    .as_ref()            )            .is_absolute(),            "file urls can't be relative paths"        );         let (explicit_max_idx, implicit_max_index) =            out.ref_map                .mappings                .iter()                .map(|m| m.spec_index)                .fold((0, 0), |(a, b), i| match i {                    SpecIndex::ExplicitInRemote(idx) => (idx.max(a), b),                    SpecIndex::Implicit(idx) => (a, idx.max(b)),                });        assert_eq!(            explicit_max_idx,            num_refspecs - 1,            "mappings don't refer to non-existing explicit refspecs"        );        assert_eq!(            implicit_max_index,            &out.ref_map.extra_refspecs.len() - 1,            "mappings don't refer to non-existing implicit refspecs"        );        let packed_refs = repo            .refs            .cached_packed_buffer()?            .expect("packed refs should be present");        assert_eq!(            repo.refs.loose_iter()?.count(),            2,            "HEAD and an actual symbolic ref we received"        );        assert_eq!(            packed_refs.iter()?.count(),            14,            "all non-symbolic refs should be stored, if reachable from our refs"        );        let sig = repo            .head()?            .log_iter()            .all()?            .expect("present")            .next()            .expect("one line")?            .signature            .to_owned();        assert_eq!(sig.name, "no name configured during clone");        assert_eq!(sig.email, "noEmailAvailable@example.com");         match out.status {            gix::remote::fetch::Status::Change { update_refs, .. } => {                for edit in &update_refs.edits {                    use gix_odb::Find;                    match edit.change.new_value().expect("always set/no deletion") {                        TargetRef::Symbolic(referent) => {                            assert!(                                repo.find_reference(referent).is_ok(),                                "if we set up a symref, the target should exist by now"                            )                        }                        TargetRef::Peeled(id) => {                            assert!(repo.objects.contains(id), "part of the fetched pack");                        }                    }                    let r = repo                        .find_reference(edit.name.as_ref())                        .unwrap_or_else(|_| panic!("didn't find created reference: {edit:?}"));                    if r.name().category().expect("known") != gix_ref::Category::Tag {                        assert!(r                            .name()                            .category_and_short_name()                            .expect("computable")                            .1                            .starts_with_str(remote_name));                        let mut logs = r.log_iter();                        assert_reflog(logs.all());                    }                }                let mut out_of_graph_tags = Vec::new();                for mapping in update_refs                    .updates                    .iter()                    .enumerate()                    .filter_map(|(idx, update)| {                        matches!(                            update.mode,                            gix::remote::fetch::refs::update::Mode::ImplicitTagNotSentByRemote                        )                        .then(|| idx)                    })                    .map(|idx| &out.ref_map.mappings[idx])                {                    out_of_graph_tags.push(                        mapping                            .remote                            .as_name()                            .expect("tag always has a path")                            .to_str()                            .expect("valid UTF8"),                    );                }                assert_eq!(                    out_of_graph_tags,                    &[                        "refs/tags/annotated-detached-tag",                        "refs/tags/annotated-future-tag",                        "refs/tags/detached-tag",                        "refs/tags/future-tag"                    ]                );            }            _ => unreachable!("clones are always causing changes and dry-runs aren't possible"),        }         let remote_head = repo            .find_reference(&format!("refs/remotes/{remote_name}/HEAD"))            .expect("remote HEAD present");        assert_eq!(            remote_head                .target()                .try_name()                .expect("remote HEAD is symbolic")                .as_bstr(),            format!("refs/remotes/{remote_name}/main"),            "it points to the local tracking branch of what the remote actually points to"        );         let head = repo.head()?;        {            let mut logs = head.log_iter();            assert_reflog(logs.all());        }         let referent = head.try_into_referent().expect("symbolic ref is present");        assert!(            referent.id().object().is_ok(),            "the object pointed to by HEAD was fetched as well"        );        assert_eq!(            referent.name().as_bstr(),            remote::repo("base").head_name()?.expect("symbolic").as_bstr(),            "local clone always adopts the name of the remote"        );         let short_name = referent.name().shorten();        assert_eq!(            repo.branch_remote_name(short_name).expect("remote is set").as_ref(),            remote_name,            "the remote branch information is fully configured"        );        assert_eq!(            repo.branch_remote_ref(short_name).expect("present")?.as_bstr(),            "refs/heads/main"        );         {            let mut logs = referent.log_iter();            assert_reflog(logs.all());        }        Ok(())    }     fn assert_reflog(log: std::io::Result<Option<gix_ref::file::log::iter::Forward<'_>>>) {        let lines = log            .unwrap()            .expect("log present")            .collect::<Result<Vec<_>, _>>()            .unwrap();        assert_eq!(lines.len(), 1, "just created");        let line = &lines[0];        assert!(            line.message.starts_with(b"clone: from "),            "{:?} unexpected",            line.message        );        let path = gix_path::from_bstr(line.message.rsplit(|b| *b == b' ').next().expect("path").as_bstr());        assert!(path.is_absolute(), "{path:?} must be absolute");    }     #[test]    fn fetch_and_checkout() -> crate::Result {        let tmp = gix_testtools::tempfile::TempDir::new()?;        let mut prepare = gix::clone::PrepareFetch::new(            remote::repo("base").path(),            tmp.path(),            gix::create::Kind::WithWorktree,            Default::default(),            restricted(),        )?;        let (mut checkout, _out) =            prepare.fetch_then_checkout(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?;        let (repo, _) = checkout.main_worktree(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?;         let index = repo.index()?;        assert_eq!(index.entries().len(), 1, "All entries are known as per HEAD tree");         let work_dir = repo.work_dir().expect("non-bare");        for entry in index.entries() {            let entry_path = work_dir.join(gix_path::from_bstr(entry.path(&index)));            assert!(entry_path.is_file(), "{entry_path:?} not found on disk")        }        Ok(())    }     #[test]    fn fetch_and_checkout_empty_remote_repo() -> crate::Result {        for version in [            gix::protocol::transport::Protocol::V0,            gix::protocol::transport::Protocol::V2,        ] {            let tmp = gix_testtools::tempfile::TempDir::new()?;            let mut prepare = gix::clone::PrepareFetch::new(                gix_testtools::scripted_fixture_read_only("make_empty_repo.sh")?,                tmp.path(),                gix::create::Kind::WithWorktree,                Default::default(),                restricted().config_overrides(Some(format!("protocol.version={}", version as u8))),            )?;            let (mut checkout, out) =                prepare.fetch_then_checkout(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?;            let (repo, _) =                checkout.main_worktree(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?;             assert!(!repo.index_path().is_file(), "newly initialized repos have no index");

Lines 1–500 of 626.