1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 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(ToString::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))
}
}
}