304 lines · 10.2 KB · Rust
RawDownload
use git_ref::bstr;use git_repository as git; mod with_core_worktree_config {    use crate::repository::worktree::Baseline;    use git_repository as git;    use std::io::BufRead;     #[test]    fn relative() -> crate::Result {        for (name, is_relative) in [("absolute-worktree", false), ("relative-worktree", true)] {            let repo = repo(name);             if is_relative {                assert_eq!(                    repo.work_dir().unwrap(),                    repo.git_dir().parent().unwrap().parent().unwrap().join("worktree"),                    "work_dir is set to core.worktree config value, relative paths are appended to `git_dir() and made absolute`"                );            } else {                assert_eq!(                    repo.work_dir().unwrap(),                    git_path::realpath(repo.git_dir().parent().unwrap().parent().unwrap().join("worktree"))?,                    "absolute workdirs are left untouched"                );            }             assert_eq!(                repo.worktree().expect("present").base(),                repo.work_dir().unwrap(),                "current worktree is based on work-tree dir"            );             let baseline = Baseline::collect(repo.git_dir())?;            assert_eq!(baseline.len(), 1, "git lists the main worktree");            assert_eq!(                baseline[0].root,                git_path::realpath(repo.git_dir().parent().unwrap())?,                "git lists the original worktree, to which we have no access anymore"            );            assert_eq!(                repo.worktrees()?.len(),                0,                "we only list linked worktrees, and there are none"            );            assert_eq!(                repo.index()?.entries().len(),                count_deleted(repo.git_dir()),                "git considers all worktree entries missing as the overridden worktree is an empty dir"            );            assert_eq!(repo.index()?.entries().len(), 3, "just to be sure");        }        Ok(())    }     #[test]    fn non_existing_relative() {        let repo = repo("relative-nonexisting-worktree");        assert_eq!(            count_deleted(repo.git_dir()),            0,            "git can't chdir into missing worktrees, has no error handling there"        );         assert_eq!(            repo.work_dir(),            repo.git_dir().parent(),            "we just ignore missing configured worktree dirs and fall back to the default one"        )    }     #[test]    fn relative_file() {        let repo = repo("relative-worktree-file");        assert_eq!(count_deleted(repo.git_dir()), 0, "git can't chdir into a file");         assert_eq!(            repo.work_dir(),            repo.git_dir().parent(),            "we just ignore missing configured worktree dirs and fall back to the default one"        )    }     #[test]    fn bare_relative() -> crate::Result {        let repo = repo("bare-relative-worktree");         assert_eq!(            count_deleted(repo.git_dir()),            0,            "git refuses to mix bare with core.worktree"        );        assert!(            repo.work_dir().is_none(),            "we simply don't load core.worktree in bare repos either to match this behaviour"        );        Ok(())    }     fn repo(name: &str) -> git::Repository {        let dir = git_testtools::scripted_fixture_repo_read_only("make_core_worktree_repo.sh").unwrap();        git::open_opts(dir.join(name), crate::restricted()).unwrap()    }     fn count_deleted(git_dir: &std::path::Path) -> usize {        std::fs::read(git_dir.join("status.baseline"))            .unwrap()            .lines()            .filter_map(Result::ok)            .filter(|line| line.contains(" D "))            .count()    }} struct Baseline<'a> {    lines: bstr::Lines<'a>,} mod baseline {    use std::path::{Path, PathBuf};     use git_object::bstr::BStr;    use git_repository::bstr::{BString, ByteSlice};     use super::Baseline;     impl<'a> Baseline<'a> {        pub fn collect(dir: impl AsRef<Path>) -> std::io::Result<Vec<Worktree>> {            let content = std::fs::read(dir.as_ref().join("worktree-list.baseline"))?;            Ok(Baseline { lines: content.lines() }.collect())        }    }     pub type Reason = BString;     #[derive(Debug)]    #[allow(dead_code)]    pub struct Worktree {        pub root: PathBuf,        pub bare: bool,        pub locked: Option<Reason>,        pub peeled: git_hash::ObjectId,        pub branch: Option<BString>,        pub prunable: Option<Reason>,    }     impl<'a> Iterator for Baseline<'a> {        type Item = Worktree;         fn next(&mut self) -> Option<Self::Item> {            let root = git_path::from_bstr(fields(self.lines.next()?).1).into_owned();            let mut bare = false;            let mut branch = None;            let mut peeled = git_hash::ObjectId::null(git_hash::Kind::Sha1);            let mut locked = None;            let mut prunable = None;            for line in self.lines.by_ref() {                if line.is_empty() {                    break;                }                if line == b"bare" {                    bare = true;                    continue;                } else if line == b"detached" {                    continue;                }                let (field, value) = fields(line);                match field {                    f if f == "HEAD" => peeled = git_hash::ObjectId::from_hex(value).expect("valid hash"),                    f if f == "branch" => branch = Some(value.to_owned()),                    f if f == "locked" => locked = Some(value.to_owned()),                    f if f == "prunable" => prunable = Some(value.to_owned()),                    _ => unreachable!("unknown field: {}", field),                }            }            Some(Worktree {                root,                bare,                locked,                peeled,                branch,                prunable,            })        }    }     fn fields(line: &[u8]) -> (&BStr, &BStr) {        let (a, b) = line.split_at(line.find_byte(b' ').expect("at least a space"));        (a.as_bstr(), b[1..].as_bstr())    }} #[test]fn from_bare_parent_repo() {    if git_testtools::should_skip_as_git_version_is_smaller_than(2, 31, 0) {        return;    }    let dir = git_testtools::scripted_fixture_repo_read_only_with_args("make_worktree_repo.sh", ["bare"]).unwrap();    let repo = git::open(dir.join("repo.git")).unwrap();     run_assertions(repo, true /* bare */);} #[test]fn from_nonbare_parent_repo() {    if git_testtools::should_skip_as_git_version_is_smaller_than(2, 31, 0) {        return;    }    let dir = git_testtools::scripted_fixture_repo_read_only("make_worktree_repo.sh").unwrap();    let repo = git::open(dir.join("repo")).unwrap();     run_assertions(repo, false /* bare */);} fn run_assertions(main_repo: git::Repository, should_be_bare: bool) {    assert_eq!(main_repo.is_bare(), should_be_bare);    let mut baseline = Baseline::collect(        main_repo            .work_dir()            .map(|p| p.parent())            .unwrap_or_else(|| main_repo.git_dir().parent())            .expect("a temp dir as parent"),    )    .unwrap();    let expected_main = baseline.remove(0);    assert_eq!(expected_main.bare, should_be_bare);     if should_be_bare {        assert!(main_repo.worktree().is_none());    } else {        assert_eq!(            main_repo.work_dir().expect("non-bare").canonicalize().unwrap(),            expected_main.root.canonicalize().unwrap()        );        assert_eq!(main_repo.head_id().unwrap(), expected_main.peeled);        assert_eq!(            main_repo.head_name().unwrap().expect("no detached head").as_bstr(),            expected_main.branch.unwrap()        );        let worktree = main_repo.worktree().expect("not bare");        assert!(            worktree.lock_reason().is_none(),            "main worktrees, bare or not, are never locked"        );        assert!(!worktree.is_locked());        assert!(worktree.is_main());    }    assert_eq!(main_repo.main_repo().unwrap(), main_repo, "main repo stays main repo");     let actual = main_repo.worktrees().unwrap();    assert_eq!(actual.len(), baseline.len());     for actual in actual {        let base = actual.base().unwrap();        let expected = baseline            .iter()            .find(|exp| exp.root == base)            .expect("we get the same root and it matches");        assert!(            !expected.bare,            "only the main worktree can be bare, and we don't see it in this loop"        );        let proxy_lock_reason = actual.lock_reason();        assert_eq!(proxy_lock_reason, expected.locked);        let proxy_is_locked = actual.is_locked();        assert_eq!(proxy_is_locked, proxy_lock_reason.is_some());        // TODO: check id of expected worktree, but need access to .gitdir from worktree base        let proxy_id = actual.id().to_owned();        assert_eq!(            base.is_dir(),            expected.prunable.is_none(),            "in our case prunable repos have no worktree base"        );         let repo = if base.is_dir() {            let repo = actual.into_repo().unwrap();            assert_eq!(                &git::open(base).unwrap(),                &repo,                "repos are considered the same no matter if opened from worktree or from git dir"            );            repo        } else {            assert!(                matches!(                    actual.clone().into_repo(),                    Err(git::worktree::proxy::into_repo::Error::MissingWorktree { .. })                ),                "missing bases are detected"            );            actual.into_repo_with_possibly_inaccessible_worktree().unwrap()        };        let worktree = repo.worktree().expect("linked worktrees have at least a base path");        assert!(!worktree.is_main());        assert_eq!(worktree.lock_reason(), proxy_lock_reason);        assert_eq!(worktree.is_locked(), proxy_is_locked);        assert_eq!(worktree.id(), Some(proxy_id.as_ref()));        assert_eq!(            repo.main_repo().unwrap(),            main_repo,            "main repo from worktree repo is the actual main repo"        );    }}