use std::{collections::HashSet, error::Error, fmt::Display};
use git2::Commit;
use indexmap::IndexMap;
use crate::{branch_name::BranchName, issue_group::IssueGroup, issue_group_map::IssueGroupMap};
#[derive(Debug)]
pub struct DisjointBranch<'repo> {
pub branch_name: BranchName,
pub commits: Vec<Commit<'repo>>,
}
#[derive(Debug)]
pub struct DisjointBranchMap<'repo>(IndexMap<IssueGroup, DisjointBranch<'repo>>);
#[derive(Debug)]
#[non_exhaustive]
pub struct FromIssueGroupMapError {
kind: FromIssueGroupMapErrorKind,
}
impl Display for FromIssueGroupMapError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.kind {
FromIssueGroupMapErrorKind::InvalidUtf8(commit) => {
write!(f, "commit summary contains invalid UTF-8: {}", commit)
}
}
}
}
impl Error for FromIssueGroupMapError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match &self.kind {
FromIssueGroupMapErrorKind::InvalidUtf8(_) => None,
}
}
}
#[derive(Debug)]
pub enum FromIssueGroupMapErrorKind {
#[non_exhaustive]
InvalidUtf8(String),
}
impl From<FromIssueGroupMapErrorKind> for FromIssueGroupMapError {
fn from(kind: FromIssueGroupMapErrorKind) -> Self {
Self { kind }
}
}
impl<'repo> DisjointBranchMap<'repo> {
pub fn iter(&self) -> indexmap::map::Iter<'_, IssueGroup, DisjointBranch<'repo>> {
self.0.iter()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl<'repo> FromIterator<(IssueGroup, DisjointBranch<'repo>)> for DisjointBranchMap<'repo> {
fn from_iter<T: IntoIterator<Item = (IssueGroup, DisjointBranch<'repo>)>>(iter: T) -> Self {
Self(iter.into_iter().collect())
}
}
impl<'repo> IntoIterator for DisjointBranchMap<'repo> {
type Item = (IssueGroup, DisjointBranch<'repo>);
type IntoIter = indexmap::map::IntoIter<IssueGroup, DisjointBranch<'repo>>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<'repo> TryFrom<IssueGroupMap<'repo>> for DisjointBranchMap<'repo> {
type Error = FromIssueGroupMapError;
fn try_from(commits_by_issue_group: IssueGroupMap<'repo>) -> Result<Self, Self::Error> {
let mut suffix: u32 = 0;
let mut seen_branch_names = HashSet::new();
commits_by_issue_group
.into_iter()
.map(|(issue_group, commits)| {
let summary = {
let commit = &commits[0];
commit.summary().ok_or_else(|| {
FromIssueGroupMapErrorKind::InvalidUtf8(commit.id().to_string())
})?
};
let generated_branch_name = BranchName::from_issue_group(&issue_group, summary);
let mut proposed_branch_name = generated_branch_name.clone();
while seen_branch_names.contains(&proposed_branch_name) {
suffix += 1;
proposed_branch_name = format!("{generated_branch_name}_{suffix}").into();
}
seen_branch_names.insert(proposed_branch_name.clone());
Ok((
issue_group,
DisjointBranch {
branch_name: proposed_branch_name,
commits,
},
))
})
.collect()
}
}