use std::collections::BTreeMap;
use bstr::BString;
use crate::{
match_group::{Outcome, Source},
RefSpec,
};
#[derive(Debug, PartialEq, Eq)]
pub enum Issue {
Conflict {
destination_full_ref_name: BString,
sources: Vec<Source>,
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(", ")
)
}
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Fix {
MappingWithPartialDestinationRemoved {
name: BString,
spec: RefSpec,
},
}
#[derive(Debug)]
pub struct Error {
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> {
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))
}
}
}