1use std::collections::BTreeMap;
2
3use crate::config::groups::{GroupDef, GroupMember, GroupMemberKind, GroupTable};
4use crate::error::GraphError;
5use crate::napi::NapiTargetsIndex;
6use crate::resolver::DependencyResolver;
7use crate::tags::TagIndex;
8use callisto_format::Versioning;
9use callisto_model::{Diagnostic, GroupName, PackageId, Severity, Version};
10
11#[derive(Clone, Debug, Default)]
12pub struct GroupCheckOutcome {
13 pub new_members: BTreeMap<GroupName, Vec<PackageId>>,
14 pub diagnostics: Vec<Diagnostic>,
15}
16
17pub fn pre_mutation_checks<D: DependencyResolver>(
18 _graph: &D,
19 groups: &GroupTable,
20 base: &BTreeMap<PackageId, Version>,
21 tags: &TagIndex,
22 _napi: &NapiTargetsIndex,
23) -> Result<GroupCheckOutcome, GraphError> {
24 let mut outcome = GroupCheckOutcome::default();
25
26 for g in groups.fixed.values() {
27 let released: Vec<PackageId> = g
28 .members(GroupMemberKind::Package)
29 .filter_map(|m| match m {
30 GroupMember::Package(ref id) => {
31 if tags.last_tag(id).is_some() {
32 Some(id.clone())
33 } else {
34 None
35 }
36 }
37 _ => None,
38 })
39 .collect();
40
41 let fresh: Vec<PackageId> = g
42 .members(GroupMemberKind::Package)
43 .filter_map(|m| match m {
44 GroupMember::Package(ref id) => {
45 if tags.last_tag(id).is_none() {
46 Some(id.clone())
47 } else {
48 None
49 }
50 }
51 _ => None,
52 })
53 .collect();
54
55 let pairs: Vec<(PackageId, Version)> = released
56 .iter()
57 .filter_map(|id| base.get(id).map(|v| (id.clone(), v.clone())))
58 .collect();
59
60 if pairs.len() > 1 {
61 let first_v = &pairs[0].1;
62 let mut divergent = false;
63 for (_id, v) in &pairs[1..] {
64 if v.grammar() != first_v.grammar() {
65 return Err(GraphError::GroupGrammarMismatch {
66 group: g.name.clone(),
67 members: pairs,
68 });
69 }
70 if Version::compare(v, first_v).ok() != Some(std::cmp::Ordering::Equal) {
71 divergent = true;
72 }
73 }
74 if divergent {
75 return Err(GraphError::FixedGroupDivergent {
76 group: g.name.clone(),
77 members: pairs,
78 });
79 }
80 }
81
82 if !fresh.is_empty() {
83 outcome.new_members.insert(g.name.clone(), fresh);
84 }
85 }
86
87 Ok(outcome)
88}
89
90pub fn fixed_group_target(
91 g: &GroupDef,
92 base: &BTreeMap<PackageId, Version>,
93 severities: &BTreeMap<PackageId, Severity>,
94 tags: &TagIndex,
95 _pre: Option<&callisto_format::PreState>,
96) -> Result<Version, GraphError> {
97 let released: Vec<PackageId> = g
98 .members(GroupMemberKind::Package)
99 .filter_map(|m| match m {
100 GroupMember::Package(ref id) => {
101 if tags.last_tag(id).is_some() {
102 Some(id.clone())
103 } else {
104 None
105 }
106 }
107 _ => None,
108 })
109 .collect();
110
111 let mut max_sev = Severity::None;
112 for m in g.members(GroupMemberKind::Package) {
113 if let GroupMember::Package(ref id) = m {
114 if let Some(&s) = severities.get(id) {
115 if s > max_sev {
116 max_sev = s;
117 }
118 }
119 }
120 }
121
122 let aligned_base = if !released.is_empty() {
123 base.get(&released[0])
124 .cloned()
125 .unwrap_or_else(|| Version::semver(1, 0, 0))
126 } else {
127 Version::semver(0, 0, 0)
128 };
129
130 let versioning = callisto_format::SemVerVersioning;
131
132 versioning
133 .bump(&aligned_base, max_sev)
134 .map_err(GraphError::Bump)
135}