ephemeris_forge_github/plan/
repository.rs1use ephemeris_forge::{FieldDiff, Limitation, NameSet, Report};
2
3use crate::api::model;
4use crate::api::request::RepositoryPatch;
5use crate::operation::repository::Operation;
6use crate::{Live, Repository};
7
8macro_rules! compare {
9 ($diff:ident, $patch:ident, $desired:ident, $live:ident, $($field:ident),+ $(,)?) => {$(
10 if let Some(wanted) = $diff.want(
11 stringify!($field),
12 $desired.$field.as_ref(),
13 &$live.$field,
14 ) {
15 $patch.$field = Some(wanted);
16 }
17 )+};
18}
19
20pub(super) struct Planned {
22 pub(super) before: Option<Operation>,
24 pub(super) after: Vec<Operation>,
26}
27
28pub(super) fn plan(desired: &Repository, state: &Live, report: &mut Report) -> Planned {
29 let live = &state.repository;
30 let mut diff = FieldDiff::default();
31 let mut patch = RepositoryPatch::default();
32 compare!(
33 diff,
34 patch,
35 desired,
36 live,
37 description,
38 homepage,
39 has_issues,
40 has_projects,
41 has_wiki,
42 has_pull_requests,
43 has_discussions,
44 pull_request_creation_policy,
45 is_template,
46 allow_squash_merge,
47 allow_merge_commit,
48 allow_rebase_merge,
49 allow_auto_merge,
50 delete_branch_on_merge,
51 allow_update_branch,
52 web_commit_signoff_required,
53 );
54 if let Some(visibility) = desired.visibility
55 && diff.check(
56 "visibility",
57 live.visibility == Some(visibility),
58 &live.visibility,
59 &Some(visibility),
60 )
61 {
62 patch.visibility = Some(visibility);
63 }
64 if let Some(branch) = &desired.default_branch
65 && diff.check(
66 "default_branch",
67 live.default_branch.as_deref() == Some(branch),
68 &live.default_branch,
69 &Some(branch),
70 )
71 {
72 patch.default_branch = Some(branch.clone());
73 }
74 if let Some(forking) = desired.allow_forking {
75 if live.allow_forking.is_none() {
76 report.limitations.push(Limitation::NotReported {
77 field: "allow_forking",
78 });
79 } else if diff.check(
80 "allow_forking",
81 live.allow_forking == Some(forking),
82 &live.allow_forking,
83 &Some(forking),
84 ) {
85 patch.allow_forking = Some(forking);
86 }
87 }
88 squash_commit(desired, live, &mut diff, &mut patch);
89 merge_commit(desired, live, &mut diff, &mut patch);
90 let immutable = immutable_releases(desired, state, &mut diff, report);
91 let archived = diff.want("archived", desired.archived.as_ref(), &live.archived);
92 let current = NameSet::new(live.topics.clone());
93 let topics = desired
94 .topics
95 .clone()
96 .map(NameSet::new)
97 .and_then(|wanted| diff.want_set("topics", &wanted, Some(¤t)))
98 .map(NameSet::into_vec);
99 diff.record("repository", report);
100 let mut after = Vec::new();
101 after.extend(topics.map(Operation::ReplaceTopics));
102 if !patch.is_empty() {
103 after.push(Operation::PatchRepository(patch));
104 }
105 after.extend(immutable.map(Operation::SetImmutableReleases));
106 let before = if archived == Some(false) {
107 archived.map(Operation::SetArchived)
108 } else {
109 after.extend(archived.map(Operation::SetArchived));
110 None
111 };
112 Planned { before, after }
113}
114
115fn immutable_releases(
116 desired: &Repository,
117 state: &Live,
118 diff: &mut FieldDiff,
119 report: &mut Report,
120) -> Option<bool> {
121 let wanted = desired.immutable_releases?;
122 let Some(live) = state.immutable_releases else {
123 report.limitations.push(Limitation::Unavailable {
124 field: "immutable_releases",
125 });
126 return None;
127 };
128 if live.enforced_by_owner && live.enabled != wanted {
129 report.limitations.push(Limitation::EnforcedByOwner {
130 field: "immutable_releases",
131 });
132 return None;
133 }
134 diff.check(
135 "immutable_releases",
136 live.enabled == wanted,
137 &live.enabled,
138 &wanted,
139 )
140 .then_some(wanted)
141}
142
143fn squash_commit(
144 desired: &Repository,
145 live: &model::Repository,
146 diff: &mut FieldDiff,
147 patch: &mut RepositoryPatch,
148) {
149 let title = desired.squash_merge_commit_title.is_some_and(|wanted| {
150 diff.check(
151 "squash_merge_commit_title",
152 live.squash_merge_commit_title == wanted,
153 &live.squash_merge_commit_title,
154 &wanted,
155 )
156 });
157 let message = desired.squash_merge_commit_message.is_some_and(|wanted| {
158 diff.check(
159 "squash_merge_commit_message",
160 live.squash_merge_commit_message == wanted,
161 &live.squash_merge_commit_message,
162 &wanted,
163 )
164 });
165 if title || message {
166 patch.squash_merge_commit_title = Some(
167 desired
168 .squash_merge_commit_title
169 .unwrap_or(live.squash_merge_commit_title),
170 );
171 patch.squash_merge_commit_message = Some(
172 desired
173 .squash_merge_commit_message
174 .unwrap_or(live.squash_merge_commit_message),
175 );
176 }
177}
178
179fn merge_commit(
180 desired: &Repository,
181 live: &model::Repository,
182 diff: &mut FieldDiff,
183 patch: &mut RepositoryPatch,
184) {
185 let title = desired.merge_commit_title.is_some_and(|wanted| {
186 diff.check(
187 "merge_commit_title",
188 live.merge_commit_title == wanted,
189 &live.merge_commit_title,
190 &wanted,
191 )
192 });
193 let message = desired.merge_commit_message.is_some_and(|wanted| {
194 diff.check(
195 "merge_commit_message",
196 live.merge_commit_message == wanted,
197 &live.merge_commit_message,
198 &wanted,
199 )
200 });
201 if title || message {
202 patch.merge_commit_title = Some(
203 desired
204 .merge_commit_title
205 .unwrap_or(live.merge_commit_title),
206 );
207 patch.merge_commit_message = Some(
208 desired
209 .merge_commit_message
210 .unwrap_or(live.merge_commit_message),
211 );
212 }
213}