diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:41 +0000 |
commit | 10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 (patch) | |
tree | bdffd5d80c26cf4a7a518281a204be1ace85b4c1 /vendor/gix-refspec/src/match_group/validate.rs | |
parent | Releasing progress-linux version 1.70.0+dfsg1-9~progress7.99u1. (diff) | |
download | rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.tar.xz rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.zip |
Merging upstream version 1.70.0+dfsg2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gix-refspec/src/match_group/validate.rs')
-rw-r--r-- | vendor/gix-refspec/src/match_group/validate.rs | 141 |
1 files changed, 141 insertions, 0 deletions
diff --git a/vendor/gix-refspec/src/match_group/validate.rs b/vendor/gix-refspec/src/match_group/validate.rs new file mode 100644 index 000000000..097a64587 --- /dev/null +++ b/vendor/gix-refspec/src/match_group/validate.rs @@ -0,0 +1,141 @@ +use std::collections::BTreeMap; + +use bstr::BString; + +use crate::{ + match_group::{Outcome, Source}, + RefSpec, +}; + +/// 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 { + /// 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: RefSpec, + }, +} + +/// 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(), + if self.issues.len() == 1 { + "issue that prevents" + } else { + "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>), 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].to_owned(), + }); + false + } + } + None => true, + }); + Ok((self, fixed)) + } + } +} |