Sebastian Thielrefactor (#571)ef7467c4 years agoHistory
313 lines · 11.3 KB · Rust
RawDownload
use git_testtools::once_cell::sync::Lazy; static BASELINE: Lazy<baseline::Baseline> = Lazy::new(|| baseline::parse().unwrap()); pub mod baseline {    use std::{borrow::Borrow, collections::HashMap};     use bstr::{BString, ByteSlice, ByteVec};    use git_hash::ObjectId;    use git_refspec::{        match_group::{validate::Fix, SourceRef},        parse::Operation,        MatchGroup,    };    use git_testtools::once_cell::sync::Lazy;     use crate::matching::BASELINE;     #[derive(Debug)]    pub struct Ref {        pub name: BString,        pub target: ObjectId,        /// Set if `target` is an annotated tag, this being the object it points to.        pub object: Option<ObjectId>,    }     impl Ref {        pub fn to_item(&self) -> git_refspec::match_group::Item<'_> {            git_refspec::match_group::Item {                full_ref_name: self.name.borrow(),                target: &self.target,                object: self.object.as_deref(),            }        }    }     static INPUT: Lazy<Vec<Ref>> = Lazy::new(|| parse_input().unwrap());     pub type Baseline = HashMap<Vec<BString>, Result<Vec<Mapping>, BString>>;     #[derive(Debug)]    pub struct Mapping {        pub remote: BString,        /// `None` if there is no destination/tracking branch        pub local: Option<BString>,    }     pub fn input() -> impl Iterator<Item = git_refspec::match_group::Item<'static>> + ExactSizeIterator + Clone {        INPUT.iter().map(Ref::to_item)    }     pub fn of_objects_with_destinations_are_written_into_given_local_branches<'a, 'b>(        specs: impl IntoIterator<Item = &'a str> + Clone,        expected: impl IntoIterator<Item = &'b str>,    ) {        agrees_and_applies_fixes(specs, Vec::new(), expected)    }     pub fn agrees_and_applies_fixes<'a, 'b>(        specs: impl IntoIterator<Item = &'a str> + Clone,        fixes: impl IntoIterator<Item = Fix>,        expected: impl IntoIterator<Item = &'b str>,    ) {        check_fetch_remote(            specs,            Mode::Custom {                expected: expected                    .into_iter()                    .map(|s| {                        let spec = git_refspec::parse(s.into(), Operation::Fetch).expect("valid spec");                        Mapping {                            remote: spec.source().unwrap().into(),                            local: spec.destination().map(ToOwned::to_owned),                        }                    })                    .collect(),                fixes: fixes.into_iter().collect(),            },        )    }     pub fn of_objects_always_matches_if_the_server_has_the_object<'a, 'b>(        specs: impl IntoIterator<Item = &'a str> + Clone,    ) {        check_fetch_remote(specs, Mode::Normal { validate_err: None })    }     pub fn agrees_with_fetch_specs<'a>(specs: impl IntoIterator<Item = &'a str> + Clone) {        check_fetch_remote(specs, Mode::Normal { validate_err: None })    }     pub fn agrees_with_fetch_specs_validation_error<'a>(        specs: impl IntoIterator<Item = &'a str> + Clone,        validate_err: impl Into<String>,    ) {        check_fetch_remote(            specs,            Mode::Normal {                validate_err: Some(validate_err.into()),            },        )    }     pub fn invalid_specs_fail_to_parse_where_git_shows_surprising_behaviour<'a>(        specs: impl IntoIterator<Item = &'a str>,        err: git_refspec::parse::Error,    ) {        let err = err.to_string();        let mut saw_err = false;        for spec in specs {            match git_refspec::parse(spec.into(), Operation::Fetch) {                Ok(_) => {}                Err(e) if e.to_string() == err => {                    saw_err = true;                }                Err(err) => panic!("Unexpected parse error: {:?}", err),            }        }        assert!(saw_err, "Failed to see error when parsing specs: {:?}", err)    }     /// Here we checked by hand which refs are actually written with a particular refspec    pub fn agrees_but_observable_refs_are_vague<'a, 'b>(        specs: impl IntoIterator<Item = &'a str> + Clone,        expected: impl IntoIterator<Item = &'b str>,    ) {        of_objects_with_destinations_are_written_into_given_local_branches(specs, expected)    }     enum Mode {        Normal { validate_err: Option<String> },        Custom { expected: Vec<Mapping>, fixes: Vec<Fix> },    }     fn check_fetch_remote<'a>(specs: impl IntoIterator<Item = &'a str> + Clone, mode: Mode) {        let match_group = MatchGroup::from_fetch_specs(            specs                .clone()                .into_iter()                .map(|spec| git_refspec::parse(spec.into(), Operation::Fetch).unwrap()),        );         let key: Vec<_> = specs.into_iter().map(BString::from).collect();        let expected = BASELINE            .get(&key)            .unwrap_or_else(|| panic!("BUG: Need {:?} added to the baseline", key))            .as_ref();         let actual = match_group.match_remotes(input()).validated();        let (actual, expected) = match &mode {            Mode::Normal { validate_err } => match validate_err {                Some(err_message) => {                    assert_eq!(actual.unwrap_err().to_string(), *err_message);                    return;                }                None => {                    let (actual, fixed) = actual.unwrap();                    assert_eq!(                        fixed,                        Vec::<git_refspec::match_group::validate::Fix>::new(),                        "we don't expect any issues to be fixed here"                    );                    (actual.mappings, expected.expect("no error"))                }            },            Mode::Custom {                expected,                fixes: expected_fixes,            } => {                let (actual, actual_fixes) = actual.unwrap();                assert_eq!(&actual_fixes, expected_fixes,);                (actual.mappings, expected)            }        };        assert_eq!(            actual.len(),            expected.len(),            "got a different amount of mappings: {:?} != {:?}",            actual,            expected        );         for (idx, (actual, expected)) in actual.iter().zip(expected).enumerate() {            assert_eq!(                source_to_bstring(actual.lhs),                expected.remote,                "{}: remote mismatch",                idx            );            if let Some(expected) = expected.local.as_ref() {                match actual.rhs.as_ref() {                    None => panic!("{}: Expected local ref to be {}, got none", idx, expected),                    Some(actual) => assert_eq!(actual.as_ref(), expected, "{}: mismatched local ref", idx),                }            }        }    }     fn source_to_bstring(source: SourceRef) -> BString {        match source {            SourceRef::FullName(name) => name.into(),            SourceRef::ObjectId(id) => id.to_string().into(),        }    }     fn parse_input() -> crate::Result<Vec<Ref>> {        let dir = git_testtools::scripted_fixture_repo_read_only("match_baseline.sh")?;        let refs_buf = std::fs::read(dir.join("clone").join("remote-refs.list"))?;        let mut out = Vec::new();        for line in refs_buf.lines() {            if line.starts_with(b"From ") {                continue;            }            let mut tokens = line.splitn(2, |b| *b == b'\t');            let target = ObjectId::from_hex(tokens.next().expect("hex-sha"))?;            let name = tokens.next().expect("name");            if !name.ends_with(b"^{}") {                out.push(Ref {                    name: name.into(),                    target,                    object: None,                })            } else {                out.last_mut().unwrap().object = Some(target);            }        }        Ok(out)    }     pub(crate) fn parse() -> crate::Result<Baseline> {        let dir = git_testtools::scripted_fixture_repo_read_only("match_baseline.sh")?;        let buf = std::fs::read(dir.join("clone").join("baseline.git"))?;         let mut map = HashMap::new();        let mut mappings = Vec::new();        let mut fatal = None;        for line in buf.lines() {            if line.starts_with(b"From ") {                continue;            }            match line.strip_prefix(b"specs: ") {                Some(specs) => {                    let key: Vec<_> = specs.split(|b| *b == b' ').map(BString::from).collect();                    let value = match fatal.take() {                        Some(message) => Err(message),                        None => Ok(std::mem::take(&mut mappings)),                    };                    map.insert(key, value);                }                None => match line.strip_prefix(b"fatal: ") {                    Some(message) => {                        fatal = Some(message.into());                    }                    None => match line.strip_prefix(b"error: * Ignoring funny ref") {                        Some(_) => continue,                        None => {                            let past_note = line                                .splitn(2, |b| *b == b']')                                .nth(1)                                .or_else(|| line.strip_prefix(b" * branch "))                                .or_else(|| line.strip_prefix(b" * tag "))                                .unwrap_or_else(|| panic!("line unhandled: {:?}", line.as_bstr()));                            let mut tokens = past_note.split(|b| *b == b' ').filter(|t| !t.is_empty());                             let mut lhs = tokens.next().unwrap().trim();                            if lhs.as_bstr() == "->" {                                lhs = "HEAD".as_bytes();                            } else {                                tokens.next();                            };                            let rhs = tokens.next().unwrap().trim();                            let local = (rhs != b"FETCH_HEAD").then(|| full_tracking_ref(rhs.into()));                            if !(lhs.as_bstr() == "HEAD" && local.is_none()) {                                mappings.push(Mapping {                                    remote: full_remote_ref(lhs.into()),                                    local,                                })                            }                        }                    },                },            }        }         Ok(map)    }     fn looks_like_tag(name: &BString) -> bool {        name.starts_with(b"v0.") || name.starts_with(b"annotated-v0.")    }     fn full_remote_ref(mut name: BString) -> BString {        if !name.contains(&b'/') || name.starts_with(b"sub/") || name.starts_with(b"suub/") {            if looks_like_tag(&name) {                name.insert_str(0, b"refs/tags/");            } else if let Ok(_id) = git_hash::ObjectId::from_hex(name.as_ref()) {                // keep as is            } else if name != "HEAD" {                name.insert_str(0, b"refs/heads/");            }        }        name    }     fn full_tracking_ref(mut name: BString) -> BString {        if name.starts_with_str(b"origin/") || name.starts_with_str("new-origin/") {            name.insert_str(0, b"refs/remotes/");        } else if looks_like_tag(&name) {            name.insert_str(0, b"refs/tags/");        }        name    }}