changeset_operations/operations/release/
types.rs1use std::collections::HashMap;
2use std::path::PathBuf;
3
4use changeset_core::{Changeset, PackageInfo, PrereleaseSpec};
5use changeset_project::GraduationState;
6use derive_builder::Builder;
7use gset::Getset;
8use indexmap::IndexMap;
9use semver::Version;
10
11use crate::types::{PackageReleaseConfig, PackageVersion};
12
13#[derive(Builder, Getset, Default)]
14#[builder(default)]
15pub struct ReleaseInput {
16 #[getset(get_copy, vis = "pub")]
17 dry_run: bool,
18 #[getset(get_copy, vis = "pub")]
19 convert_inherited: bool,
20 #[getset(get_copy, vis = "pub")]
21 no_commit: bool,
22 #[getset(get_copy, vis = "pub")]
23 no_tags: bool,
24 #[getset(get_copy, vis = "pub")]
25 keep_changesets: bool,
26 #[getset(get_copy, vis = "pub")]
27 force: bool,
28 #[getset(get, vis = "pub")]
29 per_package_config: HashMap<String, PackageReleaseConfig>,
30 #[getset(get_as_ref, vis = "pub", ty = "Option<&PrereleaseSpec>")]
31 global_prerelease: Option<PrereleaseSpec>,
32 #[getset(get_copy, vis = "pub")]
33 graduate_all: bool,
34}
35
36#[derive(Debug, Clone, Getset)]
37pub struct ChangelogUpdate {
38 #[getset(get, vis = "pub")]
39 path: PathBuf,
40 #[getset(get_as_ref, vis = "pub", ty = "Option<&String>")]
41 package: Option<String>,
42 #[getset(get, vis = "pub")]
43 version: Version,
44 #[getset(get_copy, vis = "pub")]
45 created: bool,
46}
47
48impl ChangelogUpdate {
49 pub(crate) fn new(
50 path: PathBuf,
51 package: Option<String>,
52 version: Version,
53 created: bool,
54 ) -> Self {
55 Self {
56 path,
57 package,
58 version,
59 created,
60 }
61 }
62}
63
64#[derive(Debug, Clone, Getset)]
65pub struct CommitResult {
66 #[getset(get, vis = "pub")]
67 sha: String,
68 #[getset(get, vis = "pub")]
69 message: String,
70}
71
72impl CommitResult {
73 pub(crate) fn new(sha: String, message: String) -> Self {
74 Self { sha, message }
75 }
76}
77
78#[derive(Debug, Clone, Getset)]
79pub struct TagResult {
80 #[getset(get, vis = "pub")]
81 name: String,
82 #[getset(get, vis = "pub")]
83 target_sha: String,
84}
85
86impl TagResult {
87 pub(crate) fn new(name: String, target_sha: String) -> Self {
88 Self { name, target_sha }
89 }
90}
91
92#[derive(Debug, Clone, Default, Getset)]
93pub struct GitOperationResult {
94 #[getset(get_as_ref, vis = "pub", ty = "Option<&CommitResult>")]
95 commit: Option<CommitResult>,
96 #[getset(get, vis = "pub")]
97 tags_created: Vec<TagResult>,
98 #[getset(get, vis = "pub")]
99 changesets_deleted: Vec<PathBuf>,
100}
101
102impl GitOperationResult {
103 pub(crate) fn new(
104 commit: Option<CommitResult>,
105 tags_created: Vec<TagResult>,
106 changesets_deleted: Vec<PathBuf>,
107 ) -> Self {
108 Self {
109 commit,
110 tags_created,
111 changesets_deleted,
112 }
113 }
114}
115
116#[must_use]
117#[derive(Debug, Clone, Getset)]
118pub struct ReleaseOutput {
119 #[getset(get, vis = "pub")]
120 planned_releases: Vec<PackageVersion>,
121 #[getset(get, vis = "pub")]
122 unchanged_packages: Vec<String>,
123 #[getset(get, vis = "pub")]
124 changesets_consumed: Vec<PathBuf>,
125 #[getset(get, vis = "pub")]
126 changelog_updates: Vec<ChangelogUpdate>,
127 #[getset(get_as_ref, vis = "pub", ty = "Option<&GitOperationResult>")]
128 git_result: Option<GitOperationResult>,
129}
130
131impl ReleaseOutput {
132 pub(crate) fn new(
133 planned_releases: Vec<PackageVersion>,
134 unchanged_packages: Vec<String>,
135 changesets_consumed: Vec<PathBuf>,
136 changelog_updates: Vec<ChangelogUpdate>,
137 git_result: Option<GitOperationResult>,
138 ) -> Self {
139 Self {
140 planned_releases,
141 unchanged_packages,
142 changesets_consumed,
143 changelog_updates,
144 git_result,
145 }
146 }
147
148 pub(super) fn with_git_result(self, git_result: GitOperationResult) -> Self {
149 Self {
150 git_result: Some(git_result),
151 ..self
152 }
153 }
154}
155
156#[must_use]
157#[derive(Debug)]
158pub enum ReleaseOutcome {
159 DryRun(ReleaseOutput),
160 Executed(ReleaseOutput),
161 NoChangesets,
162}
163
164pub(super) struct GitOptions {
165 pub(super) should_commit: bool,
166 pub(super) should_create_tags: bool,
167 pub(super) should_delete_changesets: bool,
168}
169
170pub(super) enum PrepareResult {
171 Ready(Box<ReleaseContext>),
172 EarlyReturn(ReleaseOutcome),
173}
174
175#[derive(Debug, Clone, Copy)]
176pub(super) struct ReleaseClassification {
177 pub(super) is_prerelease_graduation: bool,
178 pub(super) is_graduating: bool,
179 pub(super) is_prerelease_release: bool,
180}
181
182pub(super) struct ReleaseContext {
183 pub(super) project: changeset_project::CargoProject,
184 pub(super) root_config: changeset_project::RootChangesetConfig,
185 pub(super) changeset_dir: PathBuf,
186 pub(super) changeset_files: Vec<PathBuf>,
187 pub(super) prerelease_state: Option<changeset_project::PrereleaseState>,
188 pub(super) graduation_state: Option<GraduationState>,
189 pub(super) per_package_config: HashMap<String, PackageReleaseConfig>,
190 pub(super) classification: ReleaseClassification,
191 pub(super) git_options: GitOptions,
192 pub(super) inherited_packages: Vec<String>,
193}
194
195#[derive(Debug, Clone)]
196pub(crate) struct ChangelogFileState {
197 pub(crate) path: PathBuf,
198 pub(crate) original_content: Option<String>,
199 pub(crate) file_existed: bool,
200}
201
202#[derive(Debug, Clone)]
203pub(crate) struct ChangesetFileState {
204 pub(crate) path: PathBuf,
205 pub(crate) original_consumed_status: Option<String>,
206 pub(crate) backup: Option<Changeset>,
207}
208
209pub(super) struct ReleasePlan {
210 pub(super) output: ReleaseOutput,
211 pub(super) package_lookup: IndexMap<String, PackageInfo>,
212 pub(super) changelog_backups: Vec<ChangelogFileState>,
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use crate::types::PackageReleaseConfigBuilder;
219 use changeset_core::PrereleaseSpec;
220 use std::collections::HashMap;
221
222 #[test]
223 fn builder_defaults_all_false() {
224 let input = ReleaseInputBuilder::default()
225 .build()
226 .expect("all fields have defaults");
227
228 assert!(!input.dry_run());
229 assert!(!input.convert_inherited());
230 assert!(!input.no_commit());
231 assert!(!input.no_tags());
232 assert!(!input.keep_changesets());
233 assert!(!input.force());
234 assert!(!input.graduate_all());
235 assert!(input.per_package_config().is_empty());
236 assert!(input.global_prerelease().is_none());
237 }
238
239 #[test]
240 fn builder_sets_dry_run() {
241 let input = ReleaseInputBuilder::default()
242 .dry_run(true)
243 .build()
244 .expect("all fields have defaults");
245
246 assert!(input.dry_run());
247 }
248
249 #[test]
250 fn builder_sets_global_prerelease() {
251 let input = ReleaseInputBuilder::default()
252 .global_prerelease(Some(PrereleaseSpec::Alpha))
253 .build()
254 .expect("all fields have defaults");
255
256 let prerelease = input.global_prerelease();
257 assert!(prerelease.is_some());
258 assert_eq!(
259 prerelease.expect("should have prerelease").identifier(),
260 "alpha"
261 );
262 }
263
264 #[test]
265 fn builder_sets_per_package_config() {
266 let mut map = HashMap::new();
267 map.insert(
268 "crate-a".to_string(),
269 PackageReleaseConfigBuilder::default()
270 .build()
271 .expect("all fields have defaults"),
272 );
273
274 let input = ReleaseInputBuilder::default()
275 .per_package_config(map)
276 .build()
277 .expect("all fields have defaults");
278
279 assert!(input.per_package_config().contains_key("crate-a"));
280 }
281}