summaryrefslogtreecommitdiffstats
path: root/vendor/gix-refspec/src/match_group/validate.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gix-refspec/src/match_group/validate.rs')
-rw-r--r--vendor/gix-refspec/src/match_group/validate.rs141
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))
+ }
+ }
+}