1use std::str::FromStr;
2
3use crate::error::CargoResult;
4
5pub trait VersionExt {
7 fn increment_major(&mut self);
9 fn increment_minor(&mut self);
11 fn increment_patch(&mut self);
13 fn increment_alpha(&mut self) -> CargoResult<()>;
19 fn increment_beta(&mut self) -> CargoResult<()>;
25 fn increment_rc(&mut self) -> CargoResult<()>;
31 fn metadata(&mut self, metadata: &str) -> CargoResult<()>;
33 fn is_prerelease(&self) -> bool;
35}
36
37impl VersionExt for semver::Version {
38 fn increment_major(&mut self) {
39 self.major += 1;
40 self.minor = 0;
41 self.patch = 0;
42 self.pre = semver::Prerelease::EMPTY;
43 self.build = semver::BuildMetadata::EMPTY;
44 }
45
46 fn increment_minor(&mut self) {
47 self.minor += 1;
48 self.patch = 0;
49 self.pre = semver::Prerelease::EMPTY;
50 self.build = semver::BuildMetadata::EMPTY;
51 }
52
53 fn increment_patch(&mut self) {
54 self.patch += 1;
55 self.pre = semver::Prerelease::EMPTY;
56 self.build = semver::BuildMetadata::EMPTY;
57 }
58
59 fn increment_alpha(&mut self) -> CargoResult<()> {
60 if let Some((pre_ext, pre_ext_ver)) = prerelease_id_version(self)? {
61 if pre_ext == VERSION_BETA || pre_ext == VERSION_RC {
62 Err(anyhow::format_err!(
63 "unsupported release level {VERSION_ALPHA}, only major, minor, and patch are supported"
64 ))
65 } else {
66 let new_ext_ver = if pre_ext == VERSION_ALPHA {
67 pre_ext_ver.unwrap_or(0) + 1
68 } else {
69 1
70 };
71 self.pre = semver::Prerelease::new(&format!("{VERSION_ALPHA}.{new_ext_ver}"))?;
72 Ok(())
73 }
74 } else {
75 self.increment_patch();
76 self.pre = semver::Prerelease::new(&format!("{VERSION_ALPHA}.1"))?;
77 Ok(())
78 }
79 }
80
81 fn increment_beta(&mut self) -> CargoResult<()> {
82 if let Some((pre_ext, pre_ext_ver)) = prerelease_id_version(self)? {
83 if pre_ext == VERSION_RC {
84 Err(anyhow::format_err!(
85 "unsupported release level {VERSION_BETA}, only major, minor, and patch are supported"
86 ))
87 } else {
88 let new_ext_ver = if pre_ext == VERSION_BETA {
89 pre_ext_ver.unwrap_or(0) + 1
90 } else {
91 1
92 };
93 self.pre = semver::Prerelease::new(&format!("{VERSION_BETA}.{new_ext_ver}"))?;
94 Ok(())
95 }
96 } else {
97 self.increment_patch();
98 self.pre = semver::Prerelease::new(&format!("{VERSION_BETA}.1"))?;
99 Ok(())
100 }
101 }
102
103 fn increment_rc(&mut self) -> CargoResult<()> {
104 if let Some((pre_ext, pre_ext_ver)) = prerelease_id_version(self)? {
105 let new_ext_ver = if pre_ext == VERSION_RC {
106 pre_ext_ver.unwrap_or(0) + 1
107 } else {
108 1
109 };
110 self.pre = semver::Prerelease::new(&format!("{VERSION_RC}.{new_ext_ver}"))?;
111 Ok(())
112 } else {
113 self.increment_patch();
114 self.pre = semver::Prerelease::new(&format!("{VERSION_RC}.1"))?;
115 Ok(())
116 }
117 }
118
119 fn metadata(&mut self, build: &str) -> CargoResult<()> {
120 self.build = semver::BuildMetadata::new(build)?;
121 Ok(())
122 }
123
124 fn is_prerelease(&self) -> bool {
125 !self.pre.is_empty()
126 }
127}
128
129static VERSION_ALPHA: &str = "alpha";
130static VERSION_BETA: &str = "beta";
131static VERSION_RC: &str = "rc";
132
133fn prerelease_id_version(version: &semver::Version) -> CargoResult<Option<(String, Option<u64>)>> {
134 if !version.pre.is_empty() {
135 if let Some((alpha, numeric)) = version.pre.as_str().split_once('.') {
136 let alpha = alpha.to_owned();
137 let numeric = u64::from_str(numeric)
138 .map_err(|_| anyhow::format_err!("pre-release `{}` version scheme is not supported by cargo-release. Use format like `pre`, `dev`, or `alpha.1` for prerelease", version.pre))?;
139 Ok(Some((alpha, Some(numeric))))
140 } else {
141 Ok(Some((version.pre.as_str().to_owned(), None)))
142 }
143 } else {
144 Ok(None)
145 }
146}
147
148pub fn upgrade_requirement(req: &str, version: &semver::Version) -> CargoResult<Option<String>> {
150 let req_text = req.to_owned();
151 let raw_req = semver::VersionReq::parse(&req_text)
152 .expect("semver to generate valid version requirements");
153 if raw_req.comparators.is_empty() {
154 Ok(None)
156 } else {
157 let comparators: Result<Vec<_>, _> = raw_req
158 .comparators
159 .into_iter()
160 .map(|p| set_comparator(p, version))
161 .collect();
162 let comparators = comparators?;
163 let new_req = semver::VersionReq { comparators };
164 let mut new_req_text = new_req.to_string();
165 if new_req_text.starts_with('^') && !req.starts_with('^') {
166 new_req_text.remove(0);
167 }
168 #[cfg(debug_assertions)]
170 {
171 assert!(
172 new_req.matches(version),
173 "Invalid req created: {new_req_text}"
174 );
175 }
176 if new_req_text == req_text {
177 Ok(None)
178 } else {
179 Ok(Some(new_req_text))
180 }
181 }
182}
183
184fn set_comparator(
185 mut pred: semver::Comparator,
186 version: &semver::Version,
187) -> CargoResult<semver::Comparator> {
188 match pred.op {
189 semver::Op::Wildcard => {
190 pred.major = version.major;
191 if pred.minor.is_some() {
192 pred.minor = Some(version.minor);
193 }
194 if pred.patch.is_some() {
195 pred.patch = Some(version.patch);
196 }
197 Ok(pred)
198 }
199 semver::Op::Exact => Ok(assign_partial_req(version, pred)),
200 semver::Op::Greater | semver::Op::GreaterEq | semver::Op::Less | semver::Op::LessEq => Err(
201 anyhow::format_err!("support for modifying {pred} is currently unsupported"),
202 ),
203 semver::Op::Tilde => Ok(assign_partial_req(version, pred)),
204 semver::Op::Caret => Ok(assign_partial_req(version, pred)),
205 _ => {
206 log::debug!("new predicate added");
207 Err(anyhow::format_err!(
208 "support for modifying {pred} is currently unsupported"
209 ))
210 }
211 }
212}
213
214fn assign_partial_req(
215 version: &semver::Version,
216 mut pred: semver::Comparator,
217) -> semver::Comparator {
218 pred.major = version.major;
219 if pred.minor.is_some() {
220 pred.minor = Some(version.minor);
221 }
222 if pred.patch.is_some() {
223 pred.patch = Some(version.patch);
224 }
225 pred.pre = version.pre.clone();
226 pred
227}
228
229#[cfg(test)]
230mod test {
231 use super::*;
232
233 mod increment {
234 use super::*;
235
236 #[test]
237 fn alpha() {
238 let mut v = semver::Version::parse("1.0.0").unwrap();
239 v.increment_alpha().unwrap();
240 assert_eq!(v, semver::Version::parse("1.0.1-alpha.1").unwrap());
241
242 let mut v2 = semver::Version::parse("1.0.1-dev").unwrap();
243 v2.increment_alpha().unwrap();
244 assert_eq!(v2, semver::Version::parse("1.0.1-alpha.1").unwrap());
245
246 let mut v3 = semver::Version::parse("1.0.1-alpha.1").unwrap();
247 v3.increment_alpha().unwrap();
248 assert_eq!(v3, semver::Version::parse("1.0.1-alpha.2").unwrap());
249
250 let mut v4 = semver::Version::parse("1.0.1-beta.1").unwrap();
251 assert!(v4.increment_alpha().is_err());
252 }
253
254 #[test]
255 fn beta() {
256 let mut v = semver::Version::parse("1.0.0").unwrap();
257 v.increment_beta().unwrap();
258 assert_eq!(v, semver::Version::parse("1.0.1-beta.1").unwrap());
259
260 let mut v2 = semver::Version::parse("1.0.1-dev").unwrap();
261 v2.increment_beta().unwrap();
262 assert_eq!(v2, semver::Version::parse("1.0.1-beta.1").unwrap());
263
264 let mut v2 = semver::Version::parse("1.0.1-alpha.1").unwrap();
265 v2.increment_beta().unwrap();
266 assert_eq!(v2, semver::Version::parse("1.0.1-beta.1").unwrap());
267
268 let mut v3 = semver::Version::parse("1.0.1-beta.1").unwrap();
269 v3.increment_beta().unwrap();
270 assert_eq!(v3, semver::Version::parse("1.0.1-beta.2").unwrap());
271
272 let mut v4 = semver::Version::parse("1.0.1-rc.1").unwrap();
273 assert!(v4.increment_beta().is_err());
274 }
275
276 #[test]
277 fn rc() {
278 let mut v = semver::Version::parse("1.0.0").unwrap();
279 v.increment_rc().unwrap();
280 assert_eq!(v, semver::Version::parse("1.0.1-rc.1").unwrap());
281
282 let mut v2 = semver::Version::parse("1.0.1-dev").unwrap();
283 v2.increment_rc().unwrap();
284 assert_eq!(v2, semver::Version::parse("1.0.1-rc.1").unwrap());
285
286 let mut v3 = semver::Version::parse("1.0.1-rc.1").unwrap();
287 v3.increment_rc().unwrap();
288 assert_eq!(v3, semver::Version::parse("1.0.1-rc.2").unwrap());
289 }
290
291 #[test]
292 fn metadata() {
293 let mut v = semver::Version::parse("1.0.0").unwrap();
294 v.metadata("git.123456").unwrap();
295 assert_eq!(v, semver::Version::parse("1.0.0+git.123456").unwrap());
296 }
297 }
298
299 mod upgrade_requirement {
300 use super::*;
301
302 #[track_caller]
303 fn assert_req_bump<'a, O: Into<Option<&'a str>>>(version: &str, req: &str, expected: O) {
304 let version = semver::Version::parse(version).unwrap();
305 let actual = upgrade_requirement(req, &version).unwrap();
306 let expected = expected.into();
307 assert_eq!(actual.as_deref(), expected);
308 }
309
310 #[test]
311 fn wildcard_major() {
312 assert_req_bump("1.0.0", "*", None);
313 }
314
315 #[test]
316 fn wildcard_minor() {
317 assert_req_bump("1.0.0", "1.*", None);
318 assert_req_bump("1.1.0", "1.*", None);
319 assert_req_bump("2.0.0", "1.*", "2.*");
320 }
321
322 #[test]
323 fn wildcard_patch() {
324 assert_req_bump("1.0.0", "1.0.*", None);
325 assert_req_bump("1.1.0", "1.0.*", "1.1.*");
326 assert_req_bump("1.1.1", "1.0.*", "1.1.*");
327 assert_req_bump("2.0.0", "1.0.*", "2.0.*");
328 }
329
330 #[test]
331 fn caret_major() {
332 assert_req_bump("1.0.0", "1", None);
333 assert_req_bump("1.0.0", "^1", None);
334
335 assert_req_bump("1.1.0", "1", None);
336 assert_req_bump("1.1.0", "^1", None);
337
338 assert_req_bump("2.0.0", "1", "2");
339 assert_req_bump("2.0.0", "^1", "^2");
340 }
341
342 #[test]
343 fn caret_minor() {
344 assert_req_bump("1.0.0", "1.0", None);
345 assert_req_bump("1.0.0", "^1.0", None);
346
347 assert_req_bump("1.1.0", "1.0", "1.1");
348 assert_req_bump("1.1.0", "^1.0", "^1.1");
349
350 assert_req_bump("1.1.1", "1.0", "1.1");
351 assert_req_bump("1.1.1", "^1.0", "^1.1");
352
353 assert_req_bump("2.0.0", "1.0", "2.0");
354 assert_req_bump("2.0.0", "^1.0", "^2.0");
355 }
356
357 #[test]
358 fn caret_patch() {
359 assert_req_bump("1.0.0", "1.0.0", None);
360 assert_req_bump("1.0.0", "^1.0.0", None);
361
362 assert_req_bump("1.1.0", "1.0.0", "1.1.0");
363 assert_req_bump("1.1.0", "^1.0.0", "^1.1.0");
364
365 assert_req_bump("1.1.1", "1.0.0", "1.1.1");
366 assert_req_bump("1.1.1", "^1.0.0", "^1.1.1");
367
368 assert_req_bump("2.0.0", "1.0.0", "2.0.0");
369 assert_req_bump("2.0.0", "^1.0.0", "^2.0.0");
370 }
371
372 #[test]
373 fn tilde_major() {
374 assert_req_bump("1.0.0", "~1", None);
375 assert_req_bump("1.1.0", "~1", None);
376 assert_req_bump("2.0.0", "~1", "~2");
377 }
378
379 #[test]
380 fn tilde_minor() {
381 assert_req_bump("1.0.0", "~1.0", None);
382 assert_req_bump("1.1.0", "~1.0", "~1.1");
383 assert_req_bump("1.1.1", "~1.0", "~1.1");
384 assert_req_bump("2.0.0", "~1.0", "~2.0");
385 }
386
387 #[test]
388 fn tilde_patch() {
389 assert_req_bump("1.0.0", "~1.0.0", None);
390 assert_req_bump("1.1.0", "~1.0.0", "~1.1.0");
391 assert_req_bump("1.1.1", "~1.0.0", "~1.1.1");
392 assert_req_bump("2.0.0", "~1.0.0", "~2.0.0");
393 }
394
395 #[test]
396 fn equal_major() {
397 assert_req_bump("1.0.0", "=1", None);
398 assert_req_bump("1.1.0", "=1", None);
399 assert_req_bump("2.0.0", "=1", "=2");
400 }
401
402 #[test]
403 fn equal_minor() {
404 assert_req_bump("1.0.0", "=1.0", None);
405 assert_req_bump("1.1.0", "=1.0", "=1.1");
406 assert_req_bump("1.1.1", "=1.0", "=1.1");
407 assert_req_bump("2.0.0", "=1.0", "=2.0");
408 }
409
410 #[test]
411 fn equal_patch() {
412 assert_req_bump("1.0.0", "=1.0.0", None);
413 assert_req_bump("1.1.0", "=1.0.0", "=1.1.0");
414 assert_req_bump("1.1.1", "=1.0.0", "=1.1.1");
415 assert_req_bump("2.0.0", "=1.0.0", "=2.0.0");
416 }
417 }
418}