Skip to main content

git_cliff_core/
release.rs

1use std::collections::HashMap;
2
3use next_version::{NextVersion as NextVersionTrait, VersionUpdater};
4use semver::Version;
5use serde::{Deserialize, Serialize};
6use serde_json::value::Value;
7
8use crate::commit::{Commit, Range, commits_to_conventional_commits};
9use crate::config::{Bump, BumpType};
10use crate::error::Result;
11use crate::statistics::Statistics;
12#[cfg(feature = "remote")]
13use crate::{
14    contributor::RemoteContributor,
15    remote::{RemoteCommit, RemotePullRequest, RemoteReleaseMetadata},
16};
17
18/// Representation of a release.
19#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
20#[serde(rename_all(serialize = "camelCase"))]
21pub struct Release<'a> {
22    /// Release version, git tag.
23    pub version: Option<String>,
24    /// git tag's message.
25    pub message: Option<String>,
26    /// Commits made for the release.
27    #[serde(deserialize_with = "commits_to_conventional_commits")]
28    pub commits: Vec<Commit<'a>>,
29    /// Commit ID of the tag.
30    #[serde(rename = "commit_id")]
31    pub commit_id: Option<String>,
32    /// Timestamp of the release in seconds, from epoch.
33    pub timestamp: Option<i64>,
34    /// Previous release.
35    pub previous: Option<Box<Release<'a>>>,
36    /// Repository path.
37    pub repository: Option<String>,
38    /// Commit range.
39    #[serde(rename = "commit_range")]
40    pub commit_range: Option<Range>,
41    /// Submodule commits.
42    ///
43    /// Maps submodule path to a list of commits.
44    #[serde(rename = "submodule_commits")]
45    pub submodule_commits: HashMap<String, Vec<Commit<'a>>>,
46    /// Aggregated statistics computed from the release's commits.
47    pub statistics: Option<Statistics>,
48    /// Arbitrary data to be used with the `--from-context` CLI option.
49    pub extra: Option<Value>,
50    /// The type of version bump that was applied.
51    #[serde(rename = "bump_type")]
52    pub bump_type: Option<BumpType>,
53    /// Contributors.
54    #[cfg(feature = "github")]
55    pub github: RemoteReleaseMetadata,
56    /// Contributors.
57    #[cfg(feature = "gitlab")]
58    pub gitlab: RemoteReleaseMetadata,
59    /// Contributors.
60    #[cfg(feature = "gitea")]
61    pub gitea: RemoteReleaseMetadata,
62    /// Contributors.
63    #[cfg(feature = "bitbucket")]
64    pub bitbucket: RemoteReleaseMetadata,
65    /// Contributors.
66    #[cfg(feature = "azure_devops")]
67    #[serde(rename = "azure_devops")]
68    pub azure_devops: RemoteReleaseMetadata,
69}
70
71#[cfg(feature = "github")]
72crate::update_release_metadata!(github, update_github_metadata);
73
74#[cfg(feature = "gitlab")]
75crate::update_release_metadata!(gitlab, update_gitlab_metadata);
76
77#[cfg(feature = "gitea")]
78crate::update_release_metadata!(gitea, update_gitea_metadata);
79
80#[cfg(feature = "bitbucket")]
81crate::update_release_metadata!(bitbucket, update_bitbucket_metadata);
82
83#[cfg(feature = "azure_devops")]
84crate::update_release_metadata!(azure_devops, update_azure_devops_metadata);
85
86impl Release<'_> {
87    /// Calculates the next version based on the commits.
88    ///
89    /// It uses the default bump version configuration to calculate the next
90    /// version.
91    #[cfg_attr(
92        feature = "tracing",
93        tracing::instrument(
94            skip_all,
95            fields(
96                version = self.version.as_deref().unwrap_or("unreleased"),
97                commits = self.commits.len()
98            )
99        )
100    )]
101    pub fn calculate_next_version(&self) -> Result<NextVersion> {
102        crate::set_progress_message!("Calculating the next version from commits");
103        self.calculate_next_version_with_config(&Bump::default())
104    }
105
106    /// Returns a new `Release` instance with aggregated statistics populated.
107    ///
108    /// This method computes various statistics from the release data and sets
109    /// the `statistics` field. It does not modify the original release but
110    /// returns a new instance with the computed statistics included.
111    #[must_use]
112    pub fn with_statistics(mut self) -> Self {
113        self.statistics = Some((&self).into());
114        self
115    }
116
117    /// Calculates the next version based on the commits.
118    ///
119    /// It uses the given bump version configuration to calculate the next
120    /// version.
121    #[cfg_attr(
122        feature = "tracing",
123        tracing::instrument(
124            skip_all,
125            fields(
126                version = self.version.as_deref().unwrap_or("unreleased"),
127                commits = self.commits.len()
128            )
129        )
130    )]
131    pub(super) fn calculate_next_version_with_config(&self, config: &Bump) -> Result<NextVersion> {
132        crate::set_progress_message!(
133            "Calculating the next version from commits with custom bump rules"
134        );
135        match self
136            .previous
137            .as_ref()
138            .and_then(|release| release.version.clone())
139        {
140            Some(version) => {
141                let mut semver = Version::parse(&version);
142                let mut prefix = None;
143                if semver.is_err() && version.split('.').count() >= 2 {
144                    let mut found_numeric = false;
145                    for (i, c) in version.char_indices() {
146                        if c.is_numeric() && !found_numeric {
147                            found_numeric = true;
148                            let version_prefix = version[..i].to_string();
149                            let remaining = version[i..].to_string();
150                            let version = Version::parse(&remaining);
151                            if version.is_ok() {
152                                semver = version;
153                                prefix = Some(version_prefix);
154                                break;
155                            }
156                        } else if !c.is_numeric() && found_numeric {
157                            found_numeric = false;
158                        }
159                    }
160                }
161                let mut next_version = VersionUpdater::new()
162                    .with_features_always_increment_minor(
163                        config.features_always_bump_minor.unwrap_or(true),
164                    )
165                    .with_breaking_always_increment_major(
166                        config.breaking_always_bump_major.unwrap_or(true),
167                    );
168                if let Some(custom_major_increment_regex) = &config.custom_major_increment_regex {
169                    next_version = next_version
170                        .with_custom_major_increment_regex(custom_major_increment_regex)?;
171                }
172                if let Some(custom_minor_increment_regex) = &config.custom_minor_increment_regex {
173                    next_version = next_version
174                        .with_custom_minor_increment_regex(custom_minor_increment_regex)?;
175                }
176                let old_semver = semver?;
177                let (next_version, determined_bump_type) =
178                    if let Some(bump_type) = &config.bump_type {
179                        let v = match bump_type {
180                            BumpType::Major => old_semver.increment_major().to_string(),
181                            BumpType::Minor => old_semver.increment_minor().to_string(),
182                            BumpType::Patch => old_semver.increment_patch().to_string(),
183                        };
184                        (v, Some(*bump_type))
185                    } else {
186                        let new_semver = next_version.increment(
187                            &old_semver,
188                            self.commits
189                                .iter()
190                                .map(|commit| commit.message.trim_end().to_string())
191                                .collect::<Vec<String>>(),
192                        );
193                        let bump_type = determine_bump_type(&old_semver, &new_semver);
194                        (new_semver.to_string(), bump_type)
195                    };
196                let version = if let Some(prefix) = prefix {
197                    format!("{prefix}{next_version}")
198                } else {
199                    next_version
200                };
201                Ok(NextVersion {
202                    version,
203                    bump_type: determined_bump_type,
204                })
205            }
206            None => Ok(NextVersion {
207                version: config.get_initial_tag(),
208                bump_type: None,
209            }),
210        }
211    }
212}
213
214/// Representation of a calculated next version.
215#[derive(Debug, Clone, PartialEq)]
216pub struct NextVersion {
217    /// Version string.
218    pub version: String,
219    /// Type of version bump that was applied.
220    pub bump_type: Option<BumpType>,
221}
222
223/// Determines the bump type by comparing two semver versions.
224fn determine_bump_type(old: &Version, new: &Version) -> Option<BumpType> {
225    if new.major != old.major {
226        Some(BumpType::Major)
227    } else if new.minor != old.minor {
228        Some(BumpType::Minor)
229    } else if new.patch != old.patch {
230        Some(BumpType::Patch)
231    } else {
232        None
233    }
234}
235
236/// Representation of a list of releases.
237#[derive(Serialize)]
238pub struct Releases<'a> {
239    /// Releases.
240    pub releases: &'a Vec<Release<'a>>,
241}
242
243impl Releases<'_> {
244    /// Returns the list of releases as JSON.
245    pub fn as_json(&self) -> Result<String> {
246        Ok(serde_json::to_string(self.releases)?)
247    }
248}
249
250#[cfg(test)]
251mod test {
252    use pretty_assertions::assert_eq;
253
254    use super::*;
255    #[test]
256    fn bump_version() -> Result<()> {
257        fn build_release<'a>(version: &str, commits: &'a [&str]) -> Release<'a> {
258            Release {
259                version: None,
260                message: None,
261                extra: None,
262                commits: commits
263                    .iter()
264                    .map(|v| Commit::from((*v).to_string()))
265                    .collect(),
266                commit_range: None,
267                commit_id: None,
268                timestamp: None,
269                previous: Some(Box::new(Release {
270                    version: Some(String::from(version)),
271                    ..Default::default()
272                })),
273                repository: Some(String::from("/root/repo")),
274                submodule_commits: HashMap::new(),
275                statistics: None,
276                bump_type: None,
277                #[cfg(feature = "github")]
278                github: crate::remote::RemoteReleaseMetadata {
279                    contributors: vec![],
280                },
281                #[cfg(feature = "gitlab")]
282                gitlab: crate::remote::RemoteReleaseMetadata {
283                    contributors: vec![],
284                },
285                #[cfg(feature = "gitea")]
286                gitea: crate::remote::RemoteReleaseMetadata {
287                    contributors: vec![],
288                },
289                #[cfg(feature = "bitbucket")]
290                bitbucket: crate::remote::RemoteReleaseMetadata {
291                    contributors: vec![],
292                },
293                #[cfg(feature = "azure_devops")]
294                azure_devops: crate::remote::RemoteReleaseMetadata {
295                    contributors: vec![],
296                },
297            }
298        }
299
300        let test_shared = [
301            ("1.0.0", "1.1.0", vec!["feat: add xyz", "fix: fix xyz"]),
302            ("1.0.0", "1.0.1", vec!["fix: add xyz", "fix: aaaaaa"]),
303            ("1.0.0", "2.0.0", vec!["feat!: add xyz", "feat: zzz"]),
304            ("1.0.0", "2.0.0", vec!["feat!: add xyz\n", "feat: zzz\n"]),
305            ("2.0.0", "2.0.1", vec!["fix: something"]),
306            ("foo/1.0.0", "foo/1.1.0", vec![
307                "feat: add xyz",
308                "fix: fix xyz",
309            ]),
310            ("bar/1.0.0", "bar/2.0.0", vec![
311                "fix: add xyz",
312                "fix!: aaaaaa",
313            ]),
314            ("zzz-123/test/1.0.0", "zzz-123/test/1.0.1", vec![
315                "fix: aaaaaa",
316            ]),
317            ("v100.0.0", "v101.0.0", vec!["feat!: something"]),
318            ("v1.0.0-alpha.1", "v1.0.0-alpha.2", vec!["fix: minor"]),
319            ("testing/v1.0.0-beta.1", "testing/v1.0.0-beta.2", vec![
320                "feat: nice",
321            ]),
322            ("tauri-v1.5.4", "tauri-v1.6.0", vec!["feat: something"]),
323            (
324                "rocket/rocket-v4.0.0-rc.1",
325                "rocket/rocket-v4.0.0-rc.2",
326                vec!["chore!: wow"],
327            ),
328            (
329                "aaa#/@#$@9384!#%^#@#@!#!239432413-idk-9999.2200.5932-alpha.419",
330                "aaa#/@#$@9384!#%^#@#@!#!239432413-idk-9999.2200.5932-alpha.420",
331                vec!["feat: damn this is working"],
332            ),
333        ];
334
335        for (version, expected_version, commits) in test_shared.iter().chain(
336            [
337                ("0.0.1", "0.0.2", vec!["fix: fix xyz"]),
338                ("0.0.1", "0.1.0", vec!["feat: add xyz", "fix: fix xyz"]),
339                ("0.0.1", "1.0.0", vec!["feat!: add xyz", "feat: zzz"]),
340                ("0.1.0", "0.1.1", vec!["fix: fix xyz"]),
341                ("0.1.0", "0.2.0", vec!["feat: add xyz", "fix: fix xyz"]),
342                ("0.1.0", "1.0.0", vec!["feat!: add xyz", "feat: zzz"]),
343            ]
344            .iter(),
345        ) {
346            let release = build_release(version, commits);
347            let next_version = release.calculate_next_version()?.version;
348            assert_eq!(expected_version, &next_version);
349            let next_version = release
350                .calculate_next_version_with_config(&Bump::default())?
351                .version;
352            assert_eq!(expected_version, &next_version);
353        }
354
355        for (version, expected_version, commits) in test_shared.iter().chain(
356            [
357                ("0.0.1", "0.0.2", vec!["fix: fix xyz"]),
358                ("0.0.1", "0.0.2", vec!["feat: add xyz", "fix: fix xyz"]),
359                ("0.0.1", "0.0.2", vec!["feat!: add xyz", "feat: zzz"]),
360                ("0.1.0", "0.1.1", vec!["fix: fix xyz"]),
361                ("0.1.0", "0.1.1", vec!["feat: add xyz", "fix: fix xyz"]),
362                ("0.1.0", "0.2.0", vec!["feat!: add xyz", "feat: zzz"]),
363            ]
364            .iter(),
365        ) {
366            let release = build_release(version, commits);
367            let next_version = release
368                .calculate_next_version_with_config(&Bump {
369                    features_always_bump_minor: Some(false),
370                    breaking_always_bump_major: Some(false),
371                    initial_tag: None,
372                    custom_major_increment_regex: None,
373                    custom_minor_increment_regex: None,
374                    bump_type: None,
375                })?
376                .version;
377            assert_eq!(expected_version, &next_version);
378        }
379
380        for (version, expected_version, commits) in test_shared.iter().chain(
381            [
382                ("0.0.1", "0.0.2", vec!["fix: fix xyz"]),
383                ("0.0.1", "0.1.0", vec!["feat: add xyz", "fix: fix xyz"]),
384                ("0.0.1", "0.1.0", vec!["feat!: add xyz", "feat: zzz"]),
385                ("0.1.0", "0.1.1", vec!["fix: fix xyz"]),
386                ("0.1.0", "0.2.0", vec!["feat: add xyz", "fix: fix xyz"]),
387                ("0.1.0", "0.2.0", vec!["feat!: add xyz", "feat: zzz"]),
388            ]
389            .iter(),
390        ) {
391            let release = build_release(version, commits);
392            let next_version = release
393                .calculate_next_version_with_config(&Bump {
394                    features_always_bump_minor: Some(true),
395                    breaking_always_bump_major: Some(false),
396                    initial_tag: None,
397                    custom_major_increment_regex: None,
398                    custom_minor_increment_regex: None,
399                    bump_type: None,
400                })?
401                .version;
402            assert_eq!(expected_version, &next_version);
403        }
404
405        for (version, expected_version, commits) in test_shared.iter().chain(
406            [
407                ("0.0.1", "0.0.2", vec!["fix: fix xyz"]),
408                ("0.0.1", "0.0.2", vec!["feat: add xyz", "fix: fix xyz"]),
409                ("0.0.1", "1.0.0", vec!["feat!: add xyz", "feat: zzz"]),
410                ("0.1.0", "0.1.1", vec!["fix: fix xyz"]),
411                ("0.1.0", "0.1.1", vec!["feat: add xyz", "fix: fix xyz"]),
412                ("0.1.0", "1.0.0", vec!["feat!: add xyz", "feat: zzz"]),
413            ]
414            .iter(),
415        ) {
416            let release = build_release(version, commits);
417            let next_version = release
418                .calculate_next_version_with_config(&Bump {
419                    features_always_bump_minor: Some(false),
420                    breaking_always_bump_major: Some(true),
421                    initial_tag: None,
422                    custom_major_increment_regex: None,
423                    custom_minor_increment_regex: None,
424                    bump_type: None,
425                })?
426                .version;
427            assert_eq!(expected_version, &next_version);
428        }
429
430        let empty_release = Release {
431            previous: Some(Box::new(Release {
432                version: None,
433                ..Default::default()
434            })),
435            ..Default::default()
436        };
437        let result = empty_release.calculate_next_version()?;
438        assert_eq!("0.1.0", result.version);
439        assert_eq!(None, result.bump_type);
440        for (features_always_bump_minor, breaking_always_bump_major) in
441            [(true, true), (true, false), (false, true), (false, false)]
442        {
443            let result = empty_release.calculate_next_version_with_config(&Bump {
444                features_always_bump_minor: Some(features_always_bump_minor),
445                breaking_always_bump_major: Some(breaking_always_bump_major),
446                initial_tag: None,
447                custom_major_increment_regex: None,
448                custom_minor_increment_regex: None,
449                bump_type: None,
450            })?;
451            assert_eq!("0.1.0", result.version);
452            assert_eq!(None, result.bump_type);
453        }
454        Ok(())
455    }
456
457    #[test]
458    fn bump_version_type() -> Result<()> {
459        fn build_release<'a>(version: &str, commits: &'a [&str]) -> Release<'a> {
460            Release {
461                version: None,
462                commits: commits
463                    .iter()
464                    .map(|v| Commit::from((*v).to_string()))
465                    .collect(),
466                previous: Some(Box::new(Release {
467                    version: Some(String::from(version)),
468                    ..Default::default()
469                })),
470                ..Default::default()
471            }
472        }
473
474        let release = build_release("1.0.0", &["fix: something"]);
475        let result = release.calculate_next_version()?;
476        assert_eq!(Some(BumpType::Patch), result.bump_type);
477
478        let release = build_release("1.0.0", &["feat: add xyz"]);
479        let result = release.calculate_next_version()?;
480        assert_eq!(Some(BumpType::Minor), result.bump_type);
481
482        let release = build_release("1.0.0", &["feat!: breaking change"]);
483        let result = release.calculate_next_version()?;
484        assert_eq!(Some(BumpType::Major), result.bump_type);
485
486        let release = build_release("1.0.0", &["fix: something"]);
487        let result = release.calculate_next_version_with_config(&Bump {
488            bump_type: Some(BumpType::Minor),
489            ..Default::default()
490        })?;
491        assert_eq!("1.1.0", result.version);
492        assert_eq!(Some(BumpType::Minor), result.bump_type);
493
494        let release = Release {
495            previous: Some(Box::new(Release {
496                version: None,
497                ..Default::default()
498            })),
499            ..Default::default()
500        };
501        let result = release.calculate_next_version()?;
502        assert_eq!(None, result.bump_type);
503
504        Ok(())
505    }
506
507    #[test]
508    fn with_statistics() {
509        let release = Release {
510            commits: vec![],
511            timestamp: Some(1_649_373_910),
512            previous: Some(Box::new(Release {
513                timestamp: Some(1_649_201_110),
514                ..Default::default()
515            })),
516            repository: Some(String::from("/root/repo")),
517            ..Default::default()
518        };
519
520        assert!(release.statistics.is_none());
521        let release = release.with_statistics();
522        assert!(release.statistics.is_some());
523    }
524
525    #[cfg(feature = "github")]
526    #[test]
527    fn update_github_metadata() -> Result<()> {
528        use crate::remote::github::{
529            GitHubCommit, GitHubCommitAuthor, GitHubCommitDetails, GitHubCommitDetailsAuthor,
530            GitHubPullRequest, PullRequestLabel,
531        };
532
533        let mut release = Release {
534            version: None,
535            message: None,
536            extra: None,
537            commits: vec![
538                Commit::from(String::from(
539                    "1d244937ee6ceb8e0314a4a201ba93a7a61f2071 add github integration",
540                )),
541                Commit::from(String::from(
542                    "21f6aa587fcb772de13f2fde0e92697c51f84162 fix github integration",
543                )),
544                Commit::from(String::from(
545                    "35d8c6b6329ecbcf131d7df02f93c3bbc5ba5973 update metadata",
546                )),
547                Commit::from(String::from(
548                    "4d3ffe4753b923f4d7807c490e650e6624a12074 do some stuff",
549                )),
550                Commit::from(String::from(
551                    "5a55e92e5a62dc5bf9872ffb2566959fad98bd05 alright",
552                )),
553                Commit::from(String::from(
554                    "6c34967147560ea09658776d4901709139b4ad66 should be fine",
555                )),
556            ],
557            commit_range: None,
558            commit_id: None,
559            timestamp: None,
560            previous: Some(Box::new(Release {
561                version: Some(String::from("1.0.0")),
562                ..Default::default()
563            })),
564            repository: Some(String::from("/root/repo")),
565            submodule_commits: HashMap::new(),
566            statistics: None,
567            bump_type: None,
568            github: RemoteReleaseMetadata {
569                contributors: vec![],
570            },
571            #[cfg(feature = "gitlab")]
572            gitlab: RemoteReleaseMetadata {
573                contributors: vec![],
574            },
575            #[cfg(feature = "gitea")]
576            gitea: RemoteReleaseMetadata {
577                contributors: vec![],
578            },
579            #[cfg(feature = "bitbucket")]
580            bitbucket: RemoteReleaseMetadata {
581                contributors: vec![],
582            },
583            #[cfg(feature = "azure_devops")]
584            azure_devops: RemoteReleaseMetadata {
585                contributors: vec![],
586            },
587        };
588        release.update_github_metadata(
589            vec![
590                GitHubCommit {
591                    sha: String::from("1d244937ee6ceb8e0314a4a201ba93a7a61f2071"),
592                    author: Some(GitHubCommitAuthor {
593                        login: Some(String::from("orhun")),
594                    }),
595                    commit: Some(GitHubCommitDetails {
596                        author: GitHubCommitDetailsAuthor {
597                            date: String::from("2021-07-18T15:14:39+03:00"),
598                        },
599                    }),
600                },
601                GitHubCommit {
602                    sha: String::from("21f6aa587fcb772de13f2fde0e92697c51f84162"),
603                    author: Some(GitHubCommitAuthor {
604                        login: Some(String::from("orhun")),
605                    }),
606                    commit: Some(GitHubCommitDetails {
607                        author: GitHubCommitDetailsAuthor {
608                            date: String::from("2021-07-18T15:12:19+03:00"),
609                        },
610                    }),
611                },
612                GitHubCommit {
613                    sha: String::from("35d8c6b6329ecbcf131d7df02f93c3bbc5ba5973"),
614                    author: Some(GitHubCommitAuthor {
615                        login: Some(String::from("nuhro")),
616                    }),
617                    commit: Some(GitHubCommitDetails {
618                        author: GitHubCommitDetailsAuthor {
619                            date: String::from("2021-07-18T15:07:23+03:00"),
620                        },
621                    }),
622                },
623                GitHubCommit {
624                    sha: String::from("4d3ffe4753b923f4d7807c490e650e6624a12074"),
625                    author: Some(GitHubCommitAuthor {
626                        login: Some(String::from("awesome_contributor")),
627                    }),
628                    commit: Some(GitHubCommitDetails {
629                        author: GitHubCommitDetailsAuthor {
630                            date: String::from("2021-07-18T15:05:10+03:00"),
631                        },
632                    }),
633                },
634                GitHubCommit {
635                    sha: String::from("5a55e92e5a62dc5bf9872ffb2566959fad98bd05"),
636                    author: Some(GitHubCommitAuthor {
637                        login: Some(String::from("orhun")),
638                    }),
639                    commit: Some(GitHubCommitDetails {
640                        author: GitHubCommitDetailsAuthor {
641                            date: String::from("2021-07-18T15:03:30+03:00"),
642                        },
643                    }),
644                },
645                GitHubCommit {
646                    sha: String::from("6c34967147560ea09658776d4901709139b4ad66"),
647                    author: Some(GitHubCommitAuthor {
648                        login: Some(String::from("someone")),
649                    }),
650                    commit: Some(GitHubCommitDetails {
651                        author: GitHubCommitDetailsAuthor {
652                            date: String::from("2021-07-18T15:00:38+03:00"),
653                        },
654                    }),
655                },
656                GitHubCommit {
657                    sha: String::from("0c34967147560e809658776d4901709139b4ad68"),
658                    author: Some(GitHubCommitAuthor {
659                        login: Some(String::from("idk")),
660                    }),
661                    commit: Some(GitHubCommitDetails {
662                        author: GitHubCommitDetailsAuthor {
663                            date: String::from("2021-07-18T15:00:38+03:00"),
664                        },
665                    }),
666                },
667                GitHubCommit {
668                    sha: String::from("kk34967147560e809658776d4901709139b4ad68"),
669                    author: None,
670                    commit: None,
671                },
672                GitHubCommit {
673                    sha: String::new(),
674                    author: None,
675                    commit: None,
676                },
677            ]
678            .into_iter()
679            .map(|v| Box::new(v) as Box<dyn RemoteCommit>)
680            .collect(),
681            vec![
682                GitHubPullRequest {
683                    title: Some(String::from("1")),
684                    number: 42,
685                    merge_commit_sha: Some(String::from(
686                        "1d244937ee6ceb8e0314a4a201ba93a7a61f2071",
687                    )),
688                    labels: vec![PullRequestLabel {
689                        name: String::from("rust"),
690                    }],
691                },
692                GitHubPullRequest {
693                    title: Some(String::from("2")),
694                    number: 66,
695                    merge_commit_sha: Some(String::from(
696                        "21f6aa587fcb772de13f2fde0e92697c51f84162",
697                    )),
698                    labels: vec![PullRequestLabel {
699                        name: String::from("rust"),
700                    }],
701                },
702                GitHubPullRequest {
703                    title: Some(String::from("3")),
704                    number: 53,
705                    merge_commit_sha: Some(String::from(
706                        "35d8c6b6329ecbcf131d7df02f93c3bbc5ba5973",
707                    )),
708                    labels: vec![PullRequestLabel {
709                        name: String::from("deps"),
710                    }],
711                },
712                GitHubPullRequest {
713                    title: Some(String::from("4")),
714                    number: 1_000,
715                    merge_commit_sha: Some(String::from(
716                        "4d3ffe4753b923f4d7807c490e650e6624a12074",
717                    )),
718                    labels: vec![PullRequestLabel {
719                        name: String::from("deps"),
720                    }],
721                },
722                GitHubPullRequest {
723                    title: Some(String::from("5")),
724                    number: 999_999,
725                    merge_commit_sha: Some(String::from(
726                        "5a55e92e5a62dc5bf9872ffb2566959fad98bd05",
727                    )),
728                    labels: vec![PullRequestLabel {
729                        name: String::from("github"),
730                    }],
731                },
732            ]
733            .into_iter()
734            .map(|v| Box::new(v) as Box<dyn RemotePullRequest>)
735            .collect(),
736        )?;
737        #[allow(deprecated)]
738        let expected_commits = vec![
739            Commit {
740                id: String::from("1d244937ee6ceb8e0314a4a201ba93a7a61f2071"),
741                message: String::from("add github integration"),
742                github: RemoteContributor {
743                    username: Some(String::from("orhun")),
744                    pr_title: Some(String::from("1")),
745                    pr_number: Some(42),
746                    pr_labels: vec![String::from("rust")],
747                    is_first_time: false,
748                },
749                remote: Some(RemoteContributor {
750                    username: Some(String::from("orhun")),
751                    pr_title: Some(String::from("1")),
752                    pr_number: Some(42),
753                    pr_labels: vec![String::from("rust")],
754                    is_first_time: false,
755                }),
756                ..Default::default()
757            },
758            Commit {
759                id: String::from("21f6aa587fcb772de13f2fde0e92697c51f84162"),
760                message: String::from("fix github integration"),
761                github: RemoteContributor {
762                    username: Some(String::from("orhun")),
763                    pr_title: Some(String::from("2")),
764                    pr_number: Some(66),
765                    pr_labels: vec![String::from("rust")],
766                    is_first_time: false,
767                },
768                remote: Some(RemoteContributor {
769                    username: Some(String::from("orhun")),
770                    pr_title: Some(String::from("2")),
771                    pr_number: Some(66),
772                    pr_labels: vec![String::from("rust")],
773                    is_first_time: false,
774                }),
775                ..Default::default()
776            },
777            Commit {
778                id: String::from("35d8c6b6329ecbcf131d7df02f93c3bbc5ba5973"),
779                message: String::from("update metadata"),
780                github: RemoteContributor {
781                    username: Some(String::from("nuhro")),
782                    pr_title: Some(String::from("3")),
783                    pr_number: Some(53),
784                    pr_labels: vec![String::from("deps")],
785                    is_first_time: false,
786                },
787                remote: Some(RemoteContributor {
788                    username: Some(String::from("nuhro")),
789                    pr_title: Some(String::from("3")),
790                    pr_number: Some(53),
791                    pr_labels: vec![String::from("deps")],
792                    is_first_time: false,
793                }),
794                ..Default::default()
795            },
796            Commit {
797                id: String::from("4d3ffe4753b923f4d7807c490e650e6624a12074"),
798                message: String::from("do some stuff"),
799                github: RemoteContributor {
800                    username: Some(String::from("awesome_contributor")),
801                    pr_title: Some(String::from("4")),
802                    pr_number: Some(1_000),
803                    pr_labels: vec![String::from("deps")],
804                    is_first_time: false,
805                },
806                remote: Some(RemoteContributor {
807                    username: Some(String::from("awesome_contributor")),
808                    pr_title: Some(String::from("4")),
809                    pr_number: Some(1_000),
810                    pr_labels: vec![String::from("deps")],
811                    is_first_time: false,
812                }),
813                ..Default::default()
814            },
815            Commit {
816                id: String::from("5a55e92e5a62dc5bf9872ffb2566959fad98bd05"),
817                message: String::from("alright"),
818                github: RemoteContributor {
819                    username: Some(String::from("orhun")),
820                    pr_title: Some(String::from("5")),
821                    pr_number: Some(999_999),
822                    pr_labels: vec![String::from("github")],
823                    is_first_time: false,
824                },
825                remote: Some(RemoteContributor {
826                    username: Some(String::from("orhun")),
827                    pr_title: Some(String::from("5")),
828                    pr_number: Some(999_999),
829                    pr_labels: vec![String::from("github")],
830                    is_first_time: false,
831                }),
832                ..Default::default()
833            },
834            Commit {
835                id: String::from("6c34967147560ea09658776d4901709139b4ad66"),
836                message: String::from("should be fine"),
837                github: RemoteContributor {
838                    username: Some(String::from("someone")),
839                    pr_title: None,
840                    pr_number: None,
841                    pr_labels: vec![],
842                    is_first_time: false,
843                },
844                remote: Some(RemoteContributor {
845                    username: Some(String::from("someone")),
846                    pr_title: None,
847                    pr_number: None,
848                    pr_labels: vec![],
849                    is_first_time: false,
850                }),
851                ..Default::default()
852            },
853        ];
854        assert_eq!(expected_commits, release.commits);
855
856        release
857            .github
858            .contributors
859            .sort_by(|a, b| a.pr_number.cmp(&b.pr_number));
860
861        let expected_metadata = RemoteReleaseMetadata {
862            contributors: vec![
863                RemoteContributor {
864                    username: Some(String::from("someone")),
865                    pr_title: None,
866                    pr_number: None,
867                    pr_labels: vec![],
868                    is_first_time: true,
869                },
870                RemoteContributor {
871                    username: Some(String::from("orhun")),
872                    pr_title: Some(String::from("1")),
873                    pr_number: Some(42),
874                    pr_labels: vec![String::from("rust")],
875                    is_first_time: true,
876                },
877                RemoteContributor {
878                    username: Some(String::from("nuhro")),
879                    pr_title: Some(String::from("3")),
880                    pr_number: Some(53),
881                    pr_labels: vec![String::from("deps")],
882                    is_first_time: true,
883                },
884                RemoteContributor {
885                    username: Some(String::from("awesome_contributor")),
886                    pr_title: Some(String::from("4")),
887                    pr_number: Some(1_000),
888                    pr_labels: vec![String::from("deps")],
889                    is_first_time: true,
890                },
891            ],
892        };
893        assert_eq!(expected_metadata, release.github);
894
895        Ok(())
896    }
897
898    #[cfg(feature = "gitlab")]
899    #[test]
900    fn update_gitlab_metadata() -> Result<()> {
901        use crate::remote::gitlab::{GitLabCommit, GitLabMergeRequest, GitLabUser};
902
903        let mut release = Release {
904            version: None,
905            message: None,
906            extra: None,
907            commits: vec![
908                Commit::from(String::from(
909                    "1d244937ee6ceb8e0314a4a201ba93a7a61f2071 add github integration",
910                )),
911                Commit::from(String::from(
912                    "21f6aa587fcb772de13f2fde0e92697c51f84162 fix github integration",
913                )),
914                Commit::from(String::from(
915                    "35d8c6b6329ecbcf131d7df02f93c3bbc5ba5973 update metadata",
916                )),
917                Commit::from(String::from(
918                    "4d3ffe4753b923f4d7807c490e650e6624a12074 do some stuff",
919                )),
920                Commit::from(String::from(
921                    "5a55e92e5a62dc5bf9872ffb2566959fad98bd05 alright",
922                )),
923                Commit::from(String::from(
924                    "6c34967147560ea09658776d4901709139b4ad66 should be fine",
925                )),
926            ],
927            commit_range: None,
928            commit_id: None,
929            timestamp: None,
930            previous: Some(Box::new(Release {
931                version: Some(String::from("1.0.0")),
932                ..Default::default()
933            })),
934            repository: Some(String::from("/root/repo")),
935            submodule_commits: HashMap::new(),
936            statistics: None,
937            bump_type: None,
938            #[cfg(feature = "github")]
939            github: RemoteReleaseMetadata {
940                contributors: vec![],
941            },
942            #[cfg(feature = "gitlab")]
943            gitlab: RemoteReleaseMetadata {
944                contributors: vec![],
945            },
946            #[cfg(feature = "gitea")]
947            gitea: RemoteReleaseMetadata {
948                contributors: vec![],
949            },
950            #[cfg(feature = "bitbucket")]
951            bitbucket: RemoteReleaseMetadata {
952                contributors: vec![],
953            },
954            #[cfg(feature = "azure_devops")]
955            azure_devops: RemoteReleaseMetadata {
956                contributors: vec![],
957            },
958        };
959        release.update_gitlab_metadata(
960            vec![
961                GitLabCommit {
962                    id: Some(String::from("1d244937ee6ceb8e0314a4a201ba93a7a61f2071")),
963                    author_name: Some(String::from("orhun")),
964                    short_id: Some(String::new()),
965                    title: Some(String::new()),
966                    author_email: Some(String::new()),
967                    authored_date: Some(String::new()),
968                    committer_name: Some(String::new()),
969                    committer_email: Some(String::new()),
970                    committed_date: Some(String::new()),
971                    created_at: Some(String::new()),
972                    message: Some(String::new()),
973                    parent_ids: vec![],
974                    web_url: Some(String::new()),
975                },
976                GitLabCommit {
977                    id: Some(String::from("21f6aa587fcb772de13f2fde0e92697c51f84162")),
978                    author_name: Some(String::from("orhun")),
979                    short_id: Some(String::new()),
980                    title: Some(String::new()),
981                    author_email: Some(String::new()),
982                    authored_date: Some(String::new()),
983                    committer_name: Some(String::new()),
984                    committer_email: Some(String::new()),
985                    committed_date: Some(String::new()),
986                    created_at: Some(String::new()),
987                    message: Some(String::new()),
988                    parent_ids: vec![],
989                    web_url: Some(String::new()),
990                },
991                GitLabCommit {
992                    id: Some(String::from("35d8c6b6329ecbcf131d7df02f93c3bbc5ba5973")),
993                    author_name: Some(String::from("nuhro")),
994                    short_id: Some(String::new()),
995                    title: Some(String::new()),
996                    author_email: Some(String::new()),
997                    authored_date: Some(String::new()),
998                    committer_name: Some(String::new()),
999                    committer_email: Some(String::new()),
1000                    committed_date: Some(String::new()),
1001                    created_at: Some(String::new()),
1002                    message: Some(String::new()),
1003                    parent_ids: vec![],
1004                    web_url: Some(String::new()),
1005                },
1006                GitLabCommit {
1007                    id: Some(String::from("4d3ffe4753b923f4d7807c490e650e6624a12074")),
1008                    author_name: Some(String::from("awesome_contributor")),
1009                    short_id: Some(String::new()),
1010                    title: Some(String::new()),
1011                    author_email: Some(String::new()),
1012                    authored_date: Some(String::new()),
1013                    committer_name: Some(String::new()),
1014                    committer_email: Some(String::new()),
1015                    committed_date: Some(String::new()),
1016                    created_at: Some(String::new()),
1017                    message: Some(String::new()),
1018                    parent_ids: vec![],
1019                    web_url: Some(String::new()),
1020                },
1021                GitLabCommit {
1022                    id: Some(String::from("5a55e92e5a62dc5bf9872ffb2566959fad98bd05")),
1023                    author_name: Some(String::from("orhun")),
1024                    short_id: Some(String::new()),
1025                    title: Some(String::new()),
1026                    author_email: Some(String::new()),
1027                    authored_date: Some(String::new()),
1028                    committer_name: Some(String::new()),
1029                    committer_email: Some(String::new()),
1030                    committed_date: Some(String::new()),
1031                    created_at: Some(String::new()),
1032                    message: Some(String::new()),
1033                    parent_ids: vec![],
1034                    web_url: Some(String::new()),
1035                },
1036                GitLabCommit {
1037                    id: Some(String::from("6c34967147560ea09658776d4901709139b4ad66")),
1038                    author_name: Some(String::from("someone")),
1039                    short_id: Some(String::new()),
1040                    title: Some(String::new()),
1041                    author_email: Some(String::new()),
1042                    authored_date: Some(String::new()),
1043                    committer_name: Some(String::new()),
1044                    committer_email: Some(String::new()),
1045                    committed_date: Some(String::new()),
1046                    created_at: Some(String::new()),
1047                    message: Some(String::new()),
1048                    parent_ids: vec![],
1049                    web_url: Some(String::new()),
1050                },
1051                GitLabCommit {
1052                    id: Some(String::from("0c34967147560e809658776d4901709139b4ad68")),
1053                    author_name: Some(String::from("idk")),
1054                    short_id: Some(String::new()),
1055                    title: Some(String::new()),
1056                    author_email: Some(String::new()),
1057                    authored_date: Some(String::new()),
1058                    committer_name: Some(String::new()),
1059                    committer_email: Some(String::new()),
1060                    committed_date: Some(String::new()),
1061                    created_at: Some(String::new()),
1062                    message: Some(String::new()),
1063                    parent_ids: vec![],
1064                    web_url: Some(String::new()),
1065                },
1066                GitLabCommit {
1067                    id: Some(String::from("kk34967147560e809658776d4901709139b4ad68")),
1068                    author_name: Some(String::from("orhun")),
1069                    short_id: Some(String::new()),
1070                    title: Some(String::new()),
1071                    author_email: Some(String::new()),
1072                    authored_date: Some(String::new()),
1073                    committer_name: Some(String::new()),
1074                    committer_email: Some(String::new()),
1075                    committed_date: Some(String::new()),
1076                    created_at: Some(String::new()),
1077                    message: Some(String::new()),
1078                    parent_ids: vec![],
1079                    web_url: Some(String::new()),
1080                },
1081            ]
1082            .into_iter()
1083            .map(|v| Box::new(v) as Box<dyn RemoteCommit>)
1084            .collect(),
1085            vec![Box::new(GitLabMergeRequest {
1086                title: Some(String::from("1")),
1087                merge_commit_sha: Some(String::from("1d244937ee6ceb8e0314a4a201ba93a7a61f2071")),
1088                id: Some(1),
1089                iid: Some(1),
1090                project_id: Some(1),
1091                description: Some(String::new()),
1092                state: Some(String::new()),
1093                created_at: Some(String::new()),
1094                author: Some(GitLabUser {
1095                    id: Some(1),
1096                    name: Some(String::from("42")),
1097                    username: Some(String::from("42")),
1098                    state: Some(String::from("42")),
1099                    avatar_url: None,
1100                    web_url: Some(String::from("42")),
1101                }),
1102                sha: Some(String::from("1d244937ee6ceb8e0314a4a201ba93a7a61f2071")),
1103                web_url: Some(String::new()),
1104                squash_commit_sha: None,
1105                labels: vec![String::from("rust")],
1106            })],
1107        )?;
1108        #[allow(deprecated)]
1109        let expected_commits = vec![
1110            Commit {
1111                id: String::from("1d244937ee6ceb8e0314a4a201ba93a7a61f2071"),
1112                message: String::from("add github integration"),
1113                gitlab: RemoteContributor {
1114                    username: Some(String::from("orhun")),
1115                    pr_title: Some(String::from("1")),
1116                    pr_number: Some(1),
1117                    pr_labels: vec![String::from("rust")],
1118                    is_first_time: false,
1119                },
1120                remote: Some(RemoteContributor {
1121                    username: Some(String::from("orhun")),
1122                    pr_title: Some(String::from("1")),
1123                    pr_number: Some(1),
1124                    pr_labels: vec![String::from("rust")],
1125                    is_first_time: false,
1126                }),
1127                ..Default::default()
1128            },
1129            Commit {
1130                id: String::from("21f6aa587fcb772de13f2fde0e92697c51f84162"),
1131                message: String::from("fix github integration"),
1132                gitlab: RemoteContributor {
1133                    username: Some(String::from("orhun")),
1134                    pr_title: None,
1135                    pr_number: None,
1136                    pr_labels: vec![],
1137                    is_first_time: false,
1138                },
1139                remote: Some(RemoteContributor {
1140                    username: Some(String::from("orhun")),
1141                    pr_title: None,
1142                    pr_number: None,
1143                    pr_labels: vec![],
1144                    is_first_time: false,
1145                }),
1146                ..Default::default()
1147            },
1148            Commit {
1149                id: String::from("35d8c6b6329ecbcf131d7df02f93c3bbc5ba5973"),
1150                message: String::from("update metadata"),
1151                gitlab: RemoteContributor {
1152                    username: Some(String::from("nuhro")),
1153                    pr_title: None,
1154                    pr_number: None,
1155                    pr_labels: vec![],
1156                    is_first_time: false,
1157                },
1158                remote: Some(RemoteContributor {
1159                    username: Some(String::from("nuhro")),
1160                    pr_title: None,
1161                    pr_number: None,
1162                    pr_labels: vec![],
1163                    is_first_time: false,
1164                }),
1165                ..Default::default()
1166            },
1167            Commit {
1168                id: String::from("4d3ffe4753b923f4d7807c490e650e6624a12074"),
1169                message: String::from("do some stuff"),
1170                gitlab: RemoteContributor {
1171                    username: Some(String::from("awesome_contributor")),
1172                    pr_title: None,
1173                    pr_number: None,
1174                    pr_labels: vec![],
1175                    is_first_time: false,
1176                },
1177                remote: Some(RemoteContributor {
1178                    username: Some(String::from("awesome_contributor")),
1179                    pr_title: None,
1180                    pr_number: None,
1181                    pr_labels: vec![],
1182                    is_first_time: false,
1183                }),
1184                ..Default::default()
1185            },
1186            Commit {
1187                id: String::from("5a55e92e5a62dc5bf9872ffb2566959fad98bd05"),
1188                message: String::from("alright"),
1189                gitlab: RemoteContributor {
1190                    username: Some(String::from("orhun")),
1191                    pr_title: None,
1192                    pr_number: None,
1193                    pr_labels: vec![],
1194                    is_first_time: false,
1195                },
1196                remote: Some(RemoteContributor {
1197                    username: Some(String::from("orhun")),
1198                    pr_title: None,
1199                    pr_number: None,
1200                    pr_labels: vec![],
1201                    is_first_time: false,
1202                }),
1203                ..Default::default()
1204            },
1205            Commit {
1206                id: String::from("6c34967147560ea09658776d4901709139b4ad66"),
1207                message: String::from("should be fine"),
1208                gitlab: RemoteContributor {
1209                    username: Some(String::from("someone")),
1210                    pr_title: None,
1211                    pr_number: None,
1212                    pr_labels: vec![],
1213                    is_first_time: false,
1214                },
1215                remote: Some(RemoteContributor {
1216                    username: Some(String::from("someone")),
1217                    pr_title: None,
1218                    pr_number: None,
1219                    pr_labels: vec![],
1220                    is_first_time: false,
1221                }),
1222                ..Default::default()
1223            },
1224        ];
1225        assert_eq!(expected_commits, release.commits);
1226
1227        release
1228            .github
1229            .contributors
1230            .sort_by(|a, b| a.pr_number.cmp(&b.pr_number));
1231
1232        let expected_metadata = RemoteReleaseMetadata {
1233            contributors: vec![
1234                RemoteContributor {
1235                    username: Some(String::from("orhun")),
1236                    pr_title: Some(String::from("1")),
1237                    pr_number: Some(1),
1238                    pr_labels: vec![String::from("rust")],
1239                    is_first_time: false,
1240                },
1241                RemoteContributor {
1242                    username: Some(String::from("nuhro")),
1243                    pr_title: None,
1244                    pr_number: None,
1245                    pr_labels: vec![],
1246                    is_first_time: true,
1247                },
1248                RemoteContributor {
1249                    username: Some(String::from("awesome_contributor")),
1250                    pr_title: None,
1251                    pr_number: None,
1252                    pr_labels: vec![],
1253                    is_first_time: true,
1254                },
1255                RemoteContributor {
1256                    username: Some(String::from("someone")),
1257                    pr_title: None,
1258                    pr_number: None,
1259                    pr_labels: vec![],
1260                    is_first_time: true,
1261                },
1262            ],
1263        };
1264        assert_eq!(expected_metadata, release.gitlab);
1265
1266        Ok(())
1267    }
1268
1269    #[cfg(feature = "gitea")]
1270    #[test]
1271    fn update_gitea_metadata() -> Result<()> {
1272        use crate::remote::gitea::{
1273            GiteaCommit, GiteaCommitAuthor, GiteaPullRequest, PullRequestLabel,
1274        };
1275
1276        let mut release = Release {
1277            version: None,
1278            message: None,
1279            extra: None,
1280            commits: vec![
1281                Commit::from(String::from(
1282                    "1d244937ee6ceb8e0314a4a201ba93a7a61f2071 add github integration",
1283                )),
1284                Commit::from(String::from(
1285                    "21f6aa587fcb772de13f2fde0e92697c51f84162 fix github integration",
1286                )),
1287                Commit::from(String::from(
1288                    "35d8c6b6329ecbcf131d7df02f93c3bbc5ba5973 update metadata",
1289                )),
1290                Commit::from(String::from(
1291                    "4d3ffe4753b923f4d7807c490e650e6624a12074 do some stuff",
1292                )),
1293                Commit::from(String::from(
1294                    "5a55e92e5a62dc5bf9872ffb2566959fad98bd05 alright",
1295                )),
1296                Commit::from(String::from(
1297                    "6c34967147560ea09658776d4901709139b4ad66 should be fine",
1298                )),
1299            ],
1300            commit_range: None,
1301            commit_id: None,
1302            timestamp: None,
1303            previous: Some(Box::new(Release {
1304                version: Some(String::from("1.0.0")),
1305                ..Default::default()
1306            })),
1307            repository: Some(String::from("/root/repo")),
1308            submodule_commits: HashMap::new(),
1309            statistics: None,
1310            bump_type: None,
1311            #[cfg(feature = "github")]
1312            github: RemoteReleaseMetadata {
1313                contributors: vec![],
1314            },
1315            #[cfg(feature = "gitlab")]
1316            gitlab: RemoteReleaseMetadata {
1317                contributors: vec![],
1318            },
1319            #[cfg(feature = "gitea")]
1320            gitea: RemoteReleaseMetadata {
1321                contributors: vec![],
1322            },
1323            #[cfg(feature = "bitbucket")]
1324            bitbucket: RemoteReleaseMetadata {
1325                contributors: vec![],
1326            },
1327            #[cfg(feature = "azure_devops")]
1328            azure_devops: RemoteReleaseMetadata {
1329                contributors: vec![],
1330            },
1331        };
1332        release.update_gitea_metadata(
1333            vec![
1334                GiteaCommit {
1335                    sha: String::from("1d244937ee6ceb8e0314a4a201ba93a7a61f2071"),
1336                    author: Some(GiteaCommitAuthor {
1337                        login: Some(String::from("orhun")),
1338                    }),
1339                    created: String::from("2021-07-18T15:14:39+03:00"),
1340                },
1341                GiteaCommit {
1342                    sha: String::from("21f6aa587fcb772de13f2fde0e92697c51f84162"),
1343                    author: Some(GiteaCommitAuthor {
1344                        login: Some(String::from("orhun")),
1345                    }),
1346                    created: String::from("2021-07-18T15:12:19+03:00"),
1347                },
1348                GiteaCommit {
1349                    sha: String::from("35d8c6b6329ecbcf131d7df02f93c3bbc5ba5973"),
1350                    author: Some(GiteaCommitAuthor {
1351                        login: Some(String::from("nuhro")),
1352                    }),
1353                    created: String::from("2021-07-18T15:07:23+03:00"),
1354                },
1355                GiteaCommit {
1356                    sha: String::from("4d3ffe4753b923f4d7807c490e650e6624a12074"),
1357                    author: Some(GiteaCommitAuthor {
1358                        login: Some(String::from("awesome_contributor")),
1359                    }),
1360                    created: String::from("2021-07-18T15:05:10+03:00"),
1361                },
1362                GiteaCommit {
1363                    sha: String::from("5a55e92e5a62dc5bf9872ffb2566959fad98bd05"),
1364                    author: Some(GiteaCommitAuthor {
1365                        login: Some(String::from("orhun")),
1366                    }),
1367                    created: String::from("2021-07-18T15:03:30+03:00"),
1368                },
1369                GiteaCommit {
1370                    sha: String::from("6c34967147560ea09658776d4901709139b4ad66"),
1371                    author: Some(GiteaCommitAuthor {
1372                        login: Some(String::from("someone")),
1373                    }),
1374                    created: String::from("2021-07-18T15:00:38+03:00"),
1375                },
1376                GiteaCommit {
1377                    sha: String::from("0c34967147560e809658776d4901709139b4ad68"),
1378                    author: Some(GiteaCommitAuthor {
1379                        login: Some(String::from("idk")),
1380                    }),
1381                    created: String::from("2021-07-18T15:00:38+03:00"),
1382                },
1383                GiteaCommit {
1384                    sha: String::from("kk34967147560e809658776d4901709139b4ad68"),
1385                    author: None,
1386                    created: String::new(),
1387                },
1388                GiteaCommit {
1389                    sha: String::new(),
1390                    author: None,
1391                    created: String::new(),
1392                },
1393            ]
1394            .into_iter()
1395            .map(|v| Box::new(v) as Box<dyn RemoteCommit>)
1396            .collect(),
1397            vec![
1398                GiteaPullRequest {
1399                    title: Some(String::from("1")),
1400                    number: 42,
1401                    merge_commit_sha: Some(String::from(
1402                        "1d244937ee6ceb8e0314a4a201ba93a7a61f2071",
1403                    )),
1404                    labels: vec![PullRequestLabel {
1405                        name: String::from("rust"),
1406                    }],
1407                },
1408                GiteaPullRequest {
1409                    title: Some(String::from("2")),
1410                    number: 66,
1411                    merge_commit_sha: Some(String::from(
1412                        "21f6aa587fcb772de13f2fde0e92697c51f84162",
1413                    )),
1414                    labels: vec![PullRequestLabel {
1415                        name: String::from("rust"),
1416                    }],
1417                },
1418                GiteaPullRequest {
1419                    title: Some(String::from("3")),
1420                    number: 53,
1421                    merge_commit_sha: Some(String::from(
1422                        "35d8c6b6329ecbcf131d7df02f93c3bbc5ba5973",
1423                    )),
1424                    labels: vec![PullRequestLabel {
1425                        name: String::from("deps"),
1426                    }],
1427                },
1428                GiteaPullRequest {
1429                    title: Some(String::from("4")),
1430                    number: 1_000,
1431                    merge_commit_sha: Some(String::from(
1432                        "4d3ffe4753b923f4d7807c490e650e6624a12074",
1433                    )),
1434                    labels: vec![PullRequestLabel {
1435                        name: String::from("deps"),
1436                    }],
1437                },
1438                GiteaPullRequest {
1439                    title: Some(String::from("5")),
1440                    number: 999_999,
1441                    merge_commit_sha: Some(String::from(
1442                        "5a55e92e5a62dc5bf9872ffb2566959fad98bd05",
1443                    )),
1444                    labels: vec![PullRequestLabel {
1445                        name: String::from("github"),
1446                    }],
1447                },
1448            ]
1449            .into_iter()
1450            .map(|v| Box::new(v) as Box<dyn RemotePullRequest>)
1451            .collect(),
1452        )?;
1453        #[allow(deprecated)]
1454        let expected_commits = vec![
1455            Commit {
1456                id: String::from("1d244937ee6ceb8e0314a4a201ba93a7a61f2071"),
1457                message: String::from("add github integration"),
1458                gitea: RemoteContributor {
1459                    username: Some(String::from("orhun")),
1460                    pr_title: Some(String::from("1")),
1461                    pr_number: Some(42),
1462                    pr_labels: vec![String::from("rust")],
1463                    is_first_time: false,
1464                },
1465                remote: Some(RemoteContributor {
1466                    username: Some(String::from("orhun")),
1467                    pr_title: Some(String::from("1")),
1468                    pr_number: Some(42),
1469                    pr_labels: vec![String::from("rust")],
1470                    is_first_time: false,
1471                }),
1472                ..Default::default()
1473            },
1474            Commit {
1475                id: String::from("21f6aa587fcb772de13f2fde0e92697c51f84162"),
1476                message: String::from("fix github integration"),
1477                gitea: RemoteContributor {
1478                    username: Some(String::from("orhun")),
1479                    pr_title: Some(String::from("2")),
1480                    pr_number: Some(66),
1481                    pr_labels: vec![String::from("rust")],
1482                    is_first_time: false,
1483                },
1484                remote: Some(RemoteContributor {
1485                    username: Some(String::from("orhun")),
1486                    pr_title: Some(String::from("2")),
1487                    pr_number: Some(66),
1488                    pr_labels: vec![String::from("rust")],
1489                    is_first_time: false,
1490                }),
1491                ..Default::default()
1492            },
1493            Commit {
1494                id: String::from("35d8c6b6329ecbcf131d7df02f93c3bbc5ba5973"),
1495                message: String::from("update metadata"),
1496                gitea: RemoteContributor {
1497                    username: Some(String::from("nuhro")),
1498                    pr_title: Some(String::from("3")),
1499                    pr_number: Some(53),
1500                    pr_labels: vec![String::from("deps")],
1501                    is_first_time: false,
1502                },
1503                remote: Some(RemoteContributor {
1504                    username: Some(String::from("nuhro")),
1505                    pr_title: Some(String::from("3")),
1506                    pr_number: Some(53),
1507                    pr_labels: vec![String::from("deps")],
1508                    is_first_time: false,
1509                }),
1510                ..Default::default()
1511            },
1512            Commit {
1513                id: String::from("4d3ffe4753b923f4d7807c490e650e6624a12074"),
1514                message: String::from("do some stuff"),
1515                gitea: RemoteContributor {
1516                    username: Some(String::from("awesome_contributor")),
1517                    pr_title: Some(String::from("4")),
1518                    pr_number: Some(1_000),
1519                    pr_labels: vec![String::from("deps")],
1520                    is_first_time: false,
1521                },
1522                remote: Some(RemoteContributor {
1523                    username: Some(String::from("awesome_contributor")),
1524                    pr_title: Some(String::from("4")),
1525                    pr_number: Some(1_000),
1526                    pr_labels: vec![String::from("deps")],
1527                    is_first_time: false,
1528                }),
1529                ..Default::default()
1530            },
1531            Commit {
1532                id: String::from("5a55e92e5a62dc5bf9872ffb2566959fad98bd05"),
1533                message: String::from("alright"),
1534                gitea: RemoteContributor {
1535                    username: Some(String::from("orhun")),
1536                    pr_title: Some(String::from("5")),
1537                    pr_number: Some(999_999),
1538                    pr_labels: vec![String::from("github")],
1539                    is_first_time: false,
1540                },
1541                remote: Some(RemoteContributor {
1542                    username: Some(String::from("orhun")),
1543                    pr_title: Some(String::from("5")),
1544                    pr_number: Some(999_999),
1545                    pr_labels: vec![String::from("github")],
1546                    is_first_time: false,
1547                }),
1548                ..Default::default()
1549            },
1550            Commit {
1551                id: String::from("6c34967147560ea09658776d4901709139b4ad66"),
1552                message: String::from("should be fine"),
1553                gitea: RemoteContributor {
1554                    username: Some(String::from("someone")),
1555                    pr_title: None,
1556                    pr_number: None,
1557                    pr_labels: vec![],
1558                    is_first_time: false,
1559                },
1560                remote: Some(RemoteContributor {
1561                    username: Some(String::from("someone")),
1562                    pr_title: None,
1563                    pr_number: None,
1564                    pr_labels: vec![],
1565                    is_first_time: false,
1566                }),
1567                ..Default::default()
1568            },
1569        ];
1570        assert_eq!(expected_commits, release.commits);
1571
1572        release
1573            .gitea
1574            .contributors
1575            .sort_by(|a, b| a.pr_number.cmp(&b.pr_number));
1576
1577        let expected_metadata = RemoteReleaseMetadata {
1578            contributors: vec![
1579                RemoteContributor {
1580                    username: Some(String::from("someone")),
1581                    pr_title: None,
1582                    pr_number: None,
1583                    pr_labels: vec![],
1584                    is_first_time: true,
1585                },
1586                RemoteContributor {
1587                    username: Some(String::from("orhun")),
1588                    pr_title: Some(String::from("1")),
1589                    pr_number: Some(42),
1590                    pr_labels: vec![String::from("rust")],
1591                    is_first_time: true,
1592                },
1593                RemoteContributor {
1594                    username: Some(String::from("nuhro")),
1595                    pr_title: Some(String::from("3")),
1596                    pr_number: Some(53),
1597                    pr_labels: vec![String::from("deps")],
1598                    is_first_time: true,
1599                },
1600                RemoteContributor {
1601                    username: Some(String::from("awesome_contributor")),
1602                    pr_title: Some(String::from("4")),
1603                    pr_number: Some(1_000),
1604                    pr_labels: vec![String::from("deps")],
1605                    is_first_time: true,
1606                },
1607            ],
1608        };
1609        assert_eq!(expected_metadata, release.gitea);
1610
1611        Ok(())
1612    }
1613
1614    #[cfg(feature = "bitbucket")]
1615    #[test]
1616    fn update_bitbucket_metadata() -> Result<()> {
1617        use crate::remote::bitbucket::{
1618            BitbucketCommit, BitbucketCommitAuthor, BitbucketPullRequest,
1619            BitbucketPullRequestMergeCommit,
1620        };
1621
1622        let mut release = Release {
1623            version: None,
1624            message: None,
1625            extra: None,
1626            commits: vec![
1627                Commit::from(String::from(
1628                    "1d244937ee6ceb8e0314a4a201ba93a7a61f2071 add bitbucket integration",
1629                )),
1630                Commit::from(String::from(
1631                    "21f6aa587fcb772de13f2fde0e92697c51f84162 fix bitbucket integration",
1632                )),
1633                Commit::from(String::from(
1634                    "35d8c6b6329ecbcf131d7df02f93c3bbc5ba5973 update metadata",
1635                )),
1636                Commit::from(String::from(
1637                    "4d3ffe4753b923f4d7807c490e650e6624a12074 do some stuff",
1638                )),
1639                Commit::from(String::from(
1640                    "5a55e92e5a62dc5bf9872ffb2566959fad98bd05 alright",
1641                )),
1642                Commit::from(String::from(
1643                    "6c34967147560ea09658776d4901709139b4ad66 should be fine",
1644                )),
1645            ],
1646            commit_range: None,
1647            commit_id: None,
1648            timestamp: None,
1649            previous: Some(Box::new(Release {
1650                version: Some(String::from("1.0.0")),
1651                ..Default::default()
1652            })),
1653            repository: Some(String::from("/root/repo")),
1654            submodule_commits: HashMap::new(),
1655            statistics: None,
1656            bump_type: None,
1657            #[cfg(feature = "github")]
1658            github: RemoteReleaseMetadata {
1659                contributors: vec![],
1660            },
1661            #[cfg(feature = "gitlab")]
1662            gitlab: RemoteReleaseMetadata {
1663                contributors: vec![],
1664            },
1665            #[cfg(feature = "gitea")]
1666            gitea: RemoteReleaseMetadata {
1667                contributors: vec![],
1668            },
1669            #[cfg(feature = "bitbucket")]
1670            bitbucket: RemoteReleaseMetadata {
1671                contributors: vec![],
1672            },
1673            #[cfg(feature = "azure_devops")]
1674            azure_devops: RemoteReleaseMetadata {
1675                contributors: vec![],
1676            },
1677        };
1678        release.update_bitbucket_metadata(
1679            vec![
1680                BitbucketCommit {
1681                    hash: String::from("1d244937ee6ceb8e0314a4a201ba93a7a61f2071"),
1682                    author: Some(BitbucketCommitAuthor {
1683                        login: Some(String::from("orhun")),
1684                    }),
1685                    date: String::from("2021-07-18T15:14:39+03:00"),
1686                },
1687                BitbucketCommit {
1688                    hash: String::from("21f6aa587fcb772de13f2fde0e92697c51f84162"),
1689                    author: Some(BitbucketCommitAuthor {
1690                        login: Some(String::from("orhun")),
1691                    }),
1692                    date: String::from("2021-07-18T15:12:19+03:00"),
1693                },
1694                BitbucketCommit {
1695                    hash: String::from("35d8c6b6329ecbcf131d7df02f93c3bbc5ba5973"),
1696                    author: Some(BitbucketCommitAuthor {
1697                        login: Some(String::from("nuhro")),
1698                    }),
1699                    date: String::from("2021-07-18T15:07:23+03:00"),
1700                },
1701                BitbucketCommit {
1702                    hash: String::from("4d3ffe4753b923f4d7807c490e650e6624a12074"),
1703                    author: Some(BitbucketCommitAuthor {
1704                        login: Some(String::from("awesome_contributor")),
1705                    }),
1706                    date: String::from("2021-07-18T15:05:10+03:00"),
1707                },
1708                BitbucketCommit {
1709                    hash: String::from("5a55e92e5a62dc5bf9872ffb2566959fad98bd05"),
1710                    author: Some(BitbucketCommitAuthor {
1711                        login: Some(String::from("orhun")),
1712                    }),
1713                    date: String::from("2021-07-18T15:03:30+03:00"),
1714                },
1715                BitbucketCommit {
1716                    hash: String::from("6c34967147560ea09658776d4901709139b4ad66"),
1717                    author: Some(BitbucketCommitAuthor {
1718                        login: Some(String::from("someone")),
1719                    }),
1720                    date: String::from("2021-07-18T15:00:38+03:00"),
1721                },
1722                BitbucketCommit {
1723                    hash: String::from("0c34967147560e809658776d4901709139b4ad68"),
1724                    author: Some(BitbucketCommitAuthor {
1725                        login: Some(String::from("idk")),
1726                    }),
1727                    date: String::from("2021-07-18T15:00:01+03:00"),
1728                },
1729                BitbucketCommit {
1730                    hash: String::from("kk34967147560e809658776d4901709139b4ad68"),
1731                    author: Some(BitbucketCommitAuthor {
1732                        login: Some(String::from("orhun")),
1733                    }),
1734                    date: String::from("2021-07-14T21:25:24+03:00"),
1735                },
1736            ]
1737            .into_iter()
1738            .map(|v| Box::new(v) as Box<dyn RemoteCommit>)
1739            .collect(),
1740            vec![Box::new(BitbucketPullRequest {
1741                id: 1,
1742                title: Some(String::from("1")),
1743                author: BitbucketCommitAuthor {
1744                    login: Some(String::from("42")),
1745                },
1746                merge_commit: BitbucketPullRequestMergeCommit {
1747                    // Bitbucket merge commits returned in short format
1748                    hash: String::from("1d244937ee6c"),
1749                },
1750            })],
1751        )?;
1752        #[allow(deprecated)]
1753        let expected_commits = vec![
1754            Commit {
1755                id: String::from("1d244937ee6ceb8e0314a4a201ba93a7a61f2071"),
1756                message: String::from("add bitbucket integration"),
1757                bitbucket: RemoteContributor {
1758                    username: Some(String::from("orhun")),
1759                    pr_title: Some(String::from("1")),
1760                    pr_number: Some(1),
1761                    pr_labels: vec![],
1762                    is_first_time: false,
1763                },
1764                remote: Some(RemoteContributor {
1765                    username: Some(String::from("orhun")),
1766                    pr_title: Some(String::from("1")),
1767                    pr_number: Some(1),
1768                    pr_labels: vec![],
1769                    is_first_time: false,
1770                }),
1771                ..Default::default()
1772            },
1773            Commit {
1774                id: String::from("21f6aa587fcb772de13f2fde0e92697c51f84162"),
1775                message: String::from("fix bitbucket integration"),
1776                bitbucket: RemoteContributor {
1777                    username: Some(String::from("orhun")),
1778                    pr_title: None,
1779                    pr_number: None,
1780                    pr_labels: vec![],
1781                    is_first_time: false,
1782                },
1783                remote: Some(RemoteContributor {
1784                    username: Some(String::from("orhun")),
1785                    pr_title: None,
1786                    pr_number: None,
1787                    pr_labels: vec![],
1788                    is_first_time: false,
1789                }),
1790                ..Default::default()
1791            },
1792            Commit {
1793                id: String::from("35d8c6b6329ecbcf131d7df02f93c3bbc5ba5973"),
1794                message: String::from("update metadata"),
1795                bitbucket: RemoteContributor {
1796                    username: Some(String::from("nuhro")),
1797                    pr_title: None,
1798                    pr_number: None,
1799                    pr_labels: vec![],
1800                    is_first_time: false,
1801                },
1802                remote: Some(RemoteContributor {
1803                    username: Some(String::from("nuhro")),
1804                    pr_title: None,
1805                    pr_number: None,
1806                    pr_labels: vec![],
1807                    is_first_time: false,
1808                }),
1809                ..Default::default()
1810            },
1811            Commit {
1812                id: String::from("4d3ffe4753b923f4d7807c490e650e6624a12074"),
1813                message: String::from("do some stuff"),
1814                bitbucket: RemoteContributor {
1815                    username: Some(String::from("awesome_contributor")),
1816                    pr_title: None,
1817                    pr_number: None,
1818                    pr_labels: vec![],
1819                    is_first_time: false,
1820                },
1821                remote: Some(RemoteContributor {
1822                    username: Some(String::from("awesome_contributor")),
1823                    pr_title: None,
1824                    pr_number: None,
1825                    pr_labels: vec![],
1826                    is_first_time: false,
1827                }),
1828                ..Default::default()
1829            },
1830            Commit {
1831                id: String::from("5a55e92e5a62dc5bf9872ffb2566959fad98bd05"),
1832                message: String::from("alright"),
1833                bitbucket: RemoteContributor {
1834                    username: Some(String::from("orhun")),
1835                    pr_title: None,
1836                    pr_number: None,
1837                    pr_labels: vec![],
1838                    is_first_time: false,
1839                },
1840                remote: Some(RemoteContributor {
1841                    username: Some(String::from("orhun")),
1842                    pr_title: None,
1843                    pr_number: None,
1844                    pr_labels: vec![],
1845                    is_first_time: false,
1846                }),
1847                ..Default::default()
1848            },
1849            Commit {
1850                id: String::from("6c34967147560ea09658776d4901709139b4ad66"),
1851                message: String::from("should be fine"),
1852                bitbucket: RemoteContributor {
1853                    username: Some(String::from("someone")),
1854                    pr_title: None,
1855                    pr_number: None,
1856                    pr_labels: vec![],
1857                    is_first_time: false,
1858                },
1859                remote: Some(RemoteContributor {
1860                    username: Some(String::from("someone")),
1861                    pr_title: None,
1862                    pr_number: None,
1863                    pr_labels: vec![],
1864                    is_first_time: false,
1865                }),
1866                ..Default::default()
1867            },
1868        ];
1869        assert_eq!(expected_commits, release.commits);
1870
1871        release
1872            .bitbucket
1873            .contributors
1874            .sort_by(|a, b| a.pr_number.cmp(&b.pr_number));
1875
1876        let expected_metadata = RemoteReleaseMetadata {
1877            contributors: vec![
1878                RemoteContributor {
1879                    username: Some(String::from("nuhro")),
1880                    pr_title: None,
1881                    pr_number: None,
1882                    pr_labels: vec![],
1883                    is_first_time: true,
1884                },
1885                RemoteContributor {
1886                    username: Some(String::from("awesome_contributor")),
1887                    pr_title: None,
1888                    pr_number: None,
1889                    pr_labels: vec![],
1890                    is_first_time: true,
1891                },
1892                RemoteContributor {
1893                    username: Some(String::from("someone")),
1894                    pr_title: None,
1895                    pr_number: None,
1896                    pr_labels: vec![],
1897                    is_first_time: true,
1898                },
1899                RemoteContributor {
1900                    username: Some(String::from("orhun")),
1901                    pr_title: Some(String::from("1")),
1902                    pr_number: Some(1),
1903                    pr_labels: vec![],
1904                    is_first_time: false,
1905                },
1906            ],
1907        };
1908        assert_eq!(expected_metadata, release.bitbucket);
1909
1910        Ok(())
1911    }
1912
1913    #[cfg(feature = "azure_devops")]
1914    #[test]
1915    fn update_azure_devops_metadata() -> Result<()> {
1916        use crate::remote::azure_devops::{
1917            AzureDevOpsCommit, AzureDevOpsCommitAuthor, AzureDevOpsCommitRef,
1918            AzureDevOpsPullRequest,
1919        };
1920
1921        let mut release = Release {
1922            version: None,
1923            message: None,
1924            extra: None,
1925            commits: vec![
1926                Commit::from(String::from(
1927                    "1d244937ee6ceb8e0314a4a201ba93a7a61f2071 add azure devops integration",
1928                )),
1929                Commit::from(String::from(
1930                    "21f6aa587fcb772de13f2fde0e92697c51f84162 fix azure devops integration",
1931                )),
1932                Commit::from(String::from(
1933                    "35d8c6b6329ecbcf131d7df02f93c3bbc5ba5973 update metadata",
1934                )),
1935                Commit::from(String::from(
1936                    "4d3ffe4753b923f4d7807c490e650e6624a12074 do some stuff",
1937                )),
1938                Commit::from(String::from(
1939                    "5a55e92e5a62dc5bf9872ffb2566959fad98bd05 alright",
1940                )),
1941                Commit::from(String::from(
1942                    "6c34967147560ea09658776d4901709139b4ad66 should be fine",
1943                )),
1944            ],
1945            commit_range: None,
1946            commit_id: None,
1947            timestamp: None,
1948            previous: Some(Box::new(Release {
1949                version: Some(String::from("1.0.0")),
1950                ..Default::default()
1951            })),
1952            repository: Some(String::from("/root/repo")),
1953            submodule_commits: HashMap::new(),
1954            statistics: None,
1955            bump_type: None,
1956            #[cfg(feature = "github")]
1957            github: RemoteReleaseMetadata {
1958                contributors: vec![],
1959            },
1960            #[cfg(feature = "gitlab")]
1961            gitlab: RemoteReleaseMetadata {
1962                contributors: vec![],
1963            },
1964            #[cfg(feature = "gitea")]
1965            gitea: RemoteReleaseMetadata {
1966                contributors: vec![],
1967            },
1968            #[cfg(feature = "bitbucket")]
1969            bitbucket: RemoteReleaseMetadata {
1970                contributors: vec![],
1971            },
1972            #[cfg(feature = "azure_devops")]
1973            azure_devops: RemoteReleaseMetadata {
1974                contributors: vec![],
1975            },
1976        };
1977        release.update_azure_devops_metadata(
1978            vec![
1979                AzureDevOpsCommit {
1980                    commit_id: String::from("1d244937ee6ceb8e0314a4a201ba93a7a61f2071"),
1981                    author: Some(AzureDevOpsCommitAuthor {
1982                        name: Some(String::from("orhun")),
1983                        email: Some(String::from("orhun@example.com")),
1984                        date: Some(String::from("2021-07-18T15:14:39+03:00")),
1985                    }),
1986                    committer: None,
1987                },
1988                AzureDevOpsCommit {
1989                    commit_id: String::from("21f6aa587fcb772de13f2fde0e92697c51f84162"),
1990                    author: Some(AzureDevOpsCommitAuthor {
1991                        name: Some(String::from("orhun")),
1992                        email: Some(String::from("orhun@example.com")),
1993                        date: Some(String::from("2021-07-18T15:12:19+03:00")),
1994                    }),
1995                    committer: None,
1996                },
1997                AzureDevOpsCommit {
1998                    commit_id: String::from("35d8c6b6329ecbcf131d7df02f93c3bbc5ba5973"),
1999                    author: Some(AzureDevOpsCommitAuthor {
2000                        name: Some(String::from("nuhro")),
2001                        email: Some(String::from("nuhro@example.com")),
2002                        date: Some(String::from("2021-07-18T15:07:23+03:00")),
2003                    }),
2004                    committer: None,
2005                },
2006                AzureDevOpsCommit {
2007                    commit_id: String::from("4d3ffe4753b923f4d7807c490e650e6624a12074"),
2008                    author: Some(AzureDevOpsCommitAuthor {
2009                        name: Some(String::from("awesome_contributor")),
2010                        email: Some(String::from("awesome@example.com")),
2011                        date: Some(String::from("2021-07-18T15:05:10+03:00")),
2012                    }),
2013                    committer: None,
2014                },
2015                AzureDevOpsCommit {
2016                    commit_id: String::from("5a55e92e5a62dc5bf9872ffb2566959fad98bd05"),
2017                    author: Some(AzureDevOpsCommitAuthor {
2018                        name: Some(String::from("orhun")),
2019                        email: Some(String::from("orhun@example.com")),
2020                        date: Some(String::from("2021-07-18T15:03:30+03:00")),
2021                    }),
2022                    committer: None,
2023                },
2024                AzureDevOpsCommit {
2025                    commit_id: String::from("6c34967147560ea09658776d4901709139b4ad66"),
2026                    author: Some(AzureDevOpsCommitAuthor {
2027                        name: Some(String::from("someone")),
2028                        email: Some(String::from("someone@example.com")),
2029                        date: Some(String::from("2021-07-18T15:00:38+03:00")),
2030                    }),
2031                    committer: None,
2032                },
2033                AzureDevOpsCommit {
2034                    commit_id: String::from("0c34967147560e809658776d4901709139b4ad68"),
2035                    author: Some(AzureDevOpsCommitAuthor {
2036                        name: Some(String::from("idk")),
2037                        email: Some(String::from("idk@example.com")),
2038                        date: Some(String::from("2021-07-18T15:00:01+03:00")),
2039                    }),
2040                    committer: None,
2041                },
2042                AzureDevOpsCommit {
2043                    commit_id: String::from("kk34967147560e809658776d4901709139b4ad68"),
2044                    author: Some(AzureDevOpsCommitAuthor {
2045                        name: Some(String::from("orhun")),
2046                        email: Some(String::from("orhun@example.com")),
2047                        date: Some(String::from("2021-07-14T21:25:24+03:00")),
2048                    }),
2049                    committer: None,
2050                },
2051            ]
2052            .into_iter()
2053            .map(|v| Box::new(v) as Box<dyn RemoteCommit>)
2054            .collect(),
2055            vec![Box::new(AzureDevOpsPullRequest {
2056                pull_request_id: 42,
2057                title: Some(String::from("1")),
2058                status: String::from("completed"),
2059                created_by: None,
2060                last_merge_commit: Some(AzureDevOpsCommitRef {
2061                    commit_id: Some(String::from("1d244937ee6ceb8e0314a4a201ba93a7a61f2071")),
2062                }),
2063                labels: vec![],
2064            })],
2065        )?;
2066        #[allow(deprecated)]
2067        let expected_commits = vec![
2068            Commit {
2069                id: String::from("1d244937ee6ceb8e0314a4a201ba93a7a61f2071"),
2070                message: String::from("add azure devops integration"),
2071                azure_devops: RemoteContributor {
2072                    username: Some(String::from("orhun")),
2073                    pr_title: Some(String::from("1")),
2074                    pr_number: Some(42),
2075                    pr_labels: vec![],
2076                    is_first_time: false,
2077                },
2078                remote: Some(RemoteContributor {
2079                    username: Some(String::from("orhun")),
2080                    pr_title: Some(String::from("1")),
2081                    pr_number: Some(42),
2082                    pr_labels: vec![],
2083                    is_first_time: false,
2084                }),
2085                ..Default::default()
2086            },
2087            Commit {
2088                id: String::from("21f6aa587fcb772de13f2fde0e92697c51f84162"),
2089                message: String::from("fix azure devops integration"),
2090                azure_devops: RemoteContributor {
2091                    username: Some(String::from("orhun")),
2092                    pr_title: None,
2093                    pr_number: None,
2094                    pr_labels: vec![],
2095                    is_first_time: false,
2096                },
2097                remote: Some(RemoteContributor {
2098                    username: Some(String::from("orhun")),
2099                    pr_title: None,
2100                    pr_number: None,
2101                    pr_labels: vec![],
2102                    is_first_time: false,
2103                }),
2104                ..Default::default()
2105            },
2106            Commit {
2107                id: String::from("35d8c6b6329ecbcf131d7df02f93c3bbc5ba5973"),
2108                message: String::from("update metadata"),
2109                azure_devops: RemoteContributor {
2110                    username: Some(String::from("nuhro")),
2111                    pr_title: None,
2112                    pr_number: None,
2113                    pr_labels: vec![],
2114                    is_first_time: false,
2115                },
2116                remote: Some(RemoteContributor {
2117                    username: Some(String::from("nuhro")),
2118                    pr_title: None,
2119                    pr_number: None,
2120                    pr_labels: vec![],
2121                    is_first_time: false,
2122                }),
2123                ..Default::default()
2124            },
2125            Commit {
2126                id: String::from("4d3ffe4753b923f4d7807c490e650e6624a12074"),
2127                message: String::from("do some stuff"),
2128                azure_devops: RemoteContributor {
2129                    username: Some(String::from("awesome_contributor")),
2130                    pr_title: None,
2131                    pr_number: None,
2132                    pr_labels: vec![],
2133                    is_first_time: false,
2134                },
2135                remote: Some(RemoteContributor {
2136                    username: Some(String::from("awesome_contributor")),
2137                    pr_title: None,
2138                    pr_number: None,
2139                    pr_labels: vec![],
2140                    is_first_time: false,
2141                }),
2142                ..Default::default()
2143            },
2144            Commit {
2145                id: String::from("5a55e92e5a62dc5bf9872ffb2566959fad98bd05"),
2146                message: String::from("alright"),
2147                azure_devops: RemoteContributor {
2148                    username: Some(String::from("orhun")),
2149                    pr_title: None,
2150                    pr_number: None,
2151                    pr_labels: vec![],
2152                    is_first_time: false,
2153                },
2154                remote: Some(RemoteContributor {
2155                    username: Some(String::from("orhun")),
2156                    pr_title: None,
2157                    pr_number: None,
2158                    pr_labels: vec![],
2159                    is_first_time: false,
2160                }),
2161                ..Default::default()
2162            },
2163            Commit {
2164                id: String::from("6c34967147560ea09658776d4901709139b4ad66"),
2165                message: String::from("should be fine"),
2166                azure_devops: RemoteContributor {
2167                    username: Some(String::from("someone")),
2168                    pr_title: None,
2169                    pr_number: None,
2170                    pr_labels: vec![],
2171                    is_first_time: false,
2172                },
2173                remote: Some(RemoteContributor {
2174                    username: Some(String::from("someone")),
2175                    pr_title: None,
2176                    pr_number: None,
2177                    pr_labels: vec![],
2178                    is_first_time: false,
2179                }),
2180                ..Default::default()
2181            },
2182        ];
2183        assert_eq!(expected_commits, release.commits);
2184
2185        release
2186            .azure_devops
2187            .contributors
2188            .sort_by(|a, b| a.pr_number.cmp(&b.pr_number));
2189
2190        let expected_metadata = RemoteReleaseMetadata {
2191            contributors: vec![
2192                RemoteContributor {
2193                    username: Some(String::from("nuhro")),
2194                    pr_title: None,
2195                    pr_number: None,
2196                    pr_labels: vec![],
2197                    is_first_time: true,
2198                },
2199                RemoteContributor {
2200                    username: Some(String::from("awesome_contributor")),
2201                    pr_title: None,
2202                    pr_number: None,
2203                    pr_labels: vec![],
2204                    is_first_time: true,
2205                },
2206                RemoteContributor {
2207                    username: Some(String::from("someone")),
2208                    pr_title: None,
2209                    pr_number: None,
2210                    pr_labels: vec![],
2211                    is_first_time: true,
2212                },
2213                RemoteContributor {
2214                    username: Some(String::from("orhun")),
2215                    pr_title: Some(String::from("1")),
2216                    pr_number: Some(42),
2217                    pr_labels: vec![],
2218                    is_first_time: false,
2219                },
2220            ],
2221        };
2222        assert_eq!(expected_metadata, release.azure_devops);
2223
2224        Ok(())
2225    }
2226}