Sebastian Thielmake fmt429cccc4 years agoHistory
139 lines · 4.9 KB · Rust
RawDownload
use std::collections::BTreeMap; use bstr::BString; use crate::{    match_group::{Outcome, Source},    RefSpecRef,}; /// All possible issues found while validating matched mappings.#[derive(Debug, PartialEq, Eq)]pub enum Issue {    /// Multiple sources try to write the same destination.    ///    /// Note that this issue doesn't take into consideration that these sources might contain the same object behind a reference.    Conflict {        /// The unenforced full name of the reference to be written.        destination_full_ref_name: BString,        /// The list of sources that map to this destination.        sources: Vec<Source>,        /// The list of specs that caused the mapping conflict, each matching the respective one in `sources` to allow both        /// `sources` and `specs` to be zipped together.        specs: Vec<BString>,    },} impl std::fmt::Display for Issue {    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {        match self {            Issue::Conflict {                destination_full_ref_name,                sources,                specs,            } => {                write!(                    f,                    "Conflicting destination {destination_full_ref_name:?} would be written by {}",                    sources                        .iter()                        .zip(specs.iter())                        .map(|(src, spec)| format!("{src} ({spec:?})"))                        .collect::<Vec<_>>()                        .join(", ")                )            }        }    }} /// All possible fixes corrected while validating matched mappings.#[derive(Debug, PartialEq, Eq, Clone)]pub enum Fix<'a> {    /// Removed a mapping that contained a partial destination entirely.    MappingWithPartialDestinationRemoved {        /// The destination ref name that was ignored.        name: BString,        /// The spec that defined the mapping        spec: RefSpecRef<'a>,    },} /// The error returned [outcome validation][Outcome::validated()].#[derive(Debug)]pub struct Error {    /// All issues discovered during validation.    pub issues: Vec<Issue>,} impl std::fmt::Display for Error {    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {        write!(            f,            "Found {} {} the refspec mapping to be used: \n\t{}",            self.issues.len(),            (self.issues.len() == 1)                .then(|| "issue that prevents")                .unwrap_or("issues that prevent"),            self.issues                .iter()                .map(|issue| issue.to_string())                .collect::<Vec<_>>()                .join("\n\t")        )    }} impl std::error::Error for Error {} impl<'spec, 'item> Outcome<'spec, 'item> {    /// Validate all mappings or dissolve them into an error stating the discovered issues.    /// Return `(modified self, issues)` providing a fixed-up set of mappings in `self` with the fixed `issues`    /// provided as part of it.    /// Terminal issues are communicated using the [`Error`] type accordingly.    pub fn validated(mut self) -> Result<(Self, Vec<Fix<'spec>>), Error> {        let mut sources_by_destinations = BTreeMap::new();        for (dst, (spec_index, src)) in self            .mappings            .iter()            .filter_map(|m| m.rhs.as_ref().map(|dst| (dst.as_ref(), (m.spec_index, &m.lhs))))        {            let sources = sources_by_destinations.entry(dst).or_insert_with(Vec::new);            if !sources.iter().any(|(_, lhs)| lhs == &src) {                sources.push((spec_index, src))            }        }        let mut issues = Vec::new();        for (dst, conflicting_sources) in sources_by_destinations.into_iter().filter(|(_, v)| v.len() > 1) {            issues.push(Issue::Conflict {                destination_full_ref_name: dst.to_owned(),                specs: conflicting_sources                    .iter()                    .map(|(spec_idx, _)| self.group.specs[*spec_idx].to_bstring())                    .collect(),                sources: conflicting_sources.into_iter().map(|(_, src)| src.to_owned()).collect(),            })        }        if !issues.is_empty() {            Err(Error { issues })        } else {            let mut fixed = Vec::new();            let group = &self.group;            self.mappings.retain(|m| match m.rhs.as_ref() {                Some(dst) => {                    if dst.starts_with(b"refs/") || dst.as_ref() == "HEAD" {                        true                    } else {                        fixed.push(Fix::MappingWithPartialDestinationRemoved {                            name: dst.as_ref().to_owned(),                            spec: group.specs[m.spec_index],                        });                        false                    }                }                None => true,            });            Ok((self, fixed))        }    }}