git_disjoint/
disjoint_branch.rs1use std::{collections::HashSet, error::Error, fmt::Display};
2
3use git2::Commit;
4use indexmap::IndexMap;
5
6use crate::{branch_name::BranchName, issue_group::IssueGroup, issue_group_map::IssueGroupMap};
7
8#[derive(Debug)]
9pub struct DisjointBranch<'repo> {
10 pub branch_name: BranchName,
12 pub commits: Vec<Commit<'repo>>,
14}
15
16#[derive(Debug)]
17pub struct DisjointBranchMap<'repo>(IndexMap<IssueGroup, DisjointBranch<'repo>>);
18
19#[derive(Debug)]
20#[non_exhaustive]
21pub struct FromIssueGroupMapError {
22 kind: FromIssueGroupMapErrorKind,
23}
24
25impl Display for FromIssueGroupMapError {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 match &self.kind {
28 FromIssueGroupMapErrorKind::InvalidUtf8(commit) => {
29 write!(f, "commit summary contains invalid UTF-8: {}", commit)
30 }
31 }
32 }
33}
34
35impl Error for FromIssueGroupMapError {
36 fn source(&self) -> Option<&(dyn Error + 'static)> {
37 match &self.kind {
38 FromIssueGroupMapErrorKind::InvalidUtf8(_) => None,
39 }
40 }
41}
42
43#[derive(Debug)]
44pub enum FromIssueGroupMapErrorKind {
45 #[non_exhaustive]
46 InvalidUtf8(String),
47}
48
49impl From<FromIssueGroupMapErrorKind> for FromIssueGroupMapError {
50 fn from(kind: FromIssueGroupMapErrorKind) -> Self {
51 Self { kind }
52 }
53}
54
55impl<'repo> DisjointBranchMap<'repo> {
56 pub fn iter(&self) -> indexmap::map::Iter<'_, IssueGroup, DisjointBranch<'repo>> {
57 self.0.iter()
58 }
59
60 pub fn is_empty(&self) -> bool {
61 self.0.is_empty()
62 }
63}
64
65impl<'repo> FromIterator<(IssueGroup, DisjointBranch<'repo>)> for DisjointBranchMap<'repo> {
66 fn from_iter<T: IntoIterator<Item = (IssueGroup, DisjointBranch<'repo>)>>(iter: T) -> Self {
67 Self(iter.into_iter().collect())
68 }
69}
70
71impl<'repo> IntoIterator for DisjointBranchMap<'repo> {
72 type Item = (IssueGroup, DisjointBranch<'repo>);
73
74 type IntoIter = indexmap::map::IntoIter<IssueGroup, DisjointBranch<'repo>>;
75
76 fn into_iter(self) -> Self::IntoIter {
77 self.0.into_iter()
78 }
79}
80
81impl<'repo> TryFrom<IssueGroupMap<'repo>> for DisjointBranchMap<'repo> {
82 type Error = FromIssueGroupMapError;
83
84 fn try_from(commits_by_issue_group: IssueGroupMap<'repo>) -> Result<Self, Self::Error> {
91 let mut suffix: u32 = 0;
92 let mut seen_branch_names = HashSet::new();
93 commits_by_issue_group
94 .into_iter()
95 .map(|(issue_group, commits)| {
96 let summary = {
100 let commit = &commits[0];
101 commit.summary().ok_or_else(|| {
102 FromIssueGroupMapErrorKind::InvalidUtf8(commit.id().to_string())
103 })?
104 };
105 let generated_branch_name = BranchName::from_issue_group(&issue_group, summary);
106 let mut proposed_branch_name = generated_branch_name.clone();
107
108 while seen_branch_names.contains(&proposed_branch_name) {
109 suffix += 1;
110 proposed_branch_name = format!("{generated_branch_name}_{suffix}").into();
112 }
113
114 seen_branch_names.insert(proposed_branch_name.clone());
115
116 Ok((
117 issue_group,
118 DisjointBranch {
119 branch_name: proposed_branch_name,
120 commits,
121 },
122 ))
123 })
124 .collect()
125 }
126}