From ed3bd5d53d1248efbc117d4603014e2fc725e8be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Casta=C3=B1o=20Arteaga?= <tegioz@icloud.com> Date: Wed, 4 Sep 2024 17:39:20 +0200 Subject: [PATCH] Add support for remediation commits (#27) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sergio CastaƱo Arteaga <tegioz@icloud.com> --- Cargo.lock | 27 +- Cargo.toml | 1 + dco2/Cargo.toml | 1 + dco2/src/dco/check/mod.rs | 190 ++++- dco2/src/dco/check/tests.rs | 1522 ++++++++++++++++++++++++++++++++++- dco2/src/github/client.rs | 26 +- 6 files changed, 1730 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c1ae071..a437542 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -490,6 +490,7 @@ dependencies = [ "mockall", "octorust", "pem 3.0.4", + "pretty_assertions", "regex", "serde", "serde_json", @@ -540,6 +541,12 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.10.7" @@ -1388,7 +1395,7 @@ checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467" dependencies = [ "inlinable_string", "pear_codegen", - "yansi", + "yansi 1.0.1", ] [[package]] @@ -1501,6 +1508,16 @@ dependencies = [ "termtree", ] +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi 0.5.1", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -1520,7 +1537,7 @@ dependencies = [ "quote", "syn", "version_check", - "yansi", + "yansi 1.0.1", ] [[package]] @@ -2765,6 +2782,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + [[package]] name = "yansi" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index a492fcb..f5a07ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ lambda_http = "0.13.0" mockall = "0.13.0" octorust = "0.8.0-rc.1" pem = "3.0.4" +pretty_assertions = "1.4.0" regex = "1.10.6" serde = { version = "1.0.209", features = ["derive"] } serde_json = "1.0.127" diff --git a/dco2/Cargo.toml b/dco2/Cargo.toml index 1747bae..41f82d9 100644 --- a/dco2/Cargo.toml +++ b/dco2/Cargo.toml @@ -28,3 +28,4 @@ tracing = { workspace = true } [dev-dependencies] indoc = { workspace = true } mockall = { workspace = true } +pretty_assertions = { workspace = true } diff --git a/dco2/src/dco/check/mod.rs b/dco2/src/dco/check/mod.rs index 225a70b..a024b9d 100644 --- a/dco2/src/dco/check/mod.rs +++ b/dco2/src/dco/check/mod.rs @@ -1,6 +1,7 @@ //! This module contains the DCO check logic. -use crate::github::{Commit, Config}; +use crate::github::{Commit, Config, ConfigAllowRemediationCommits, GitUser}; +use anyhow::{bail, Result}; use askama::Template; use email_address::EmailAddress; use regex::Regex; @@ -67,14 +68,18 @@ pub(crate) enum CommitSuccessReason { FromBot, IsMerge, ValidSignOff, + ValidSignOffInRemediationCommit, } impl Display for CommitSuccessReason { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - CommitSuccessReason::FromBot => write!(f, "sign-off not required in bot commit"), - CommitSuccessReason::IsMerge => write!(f, "sign-off not required in merge commit"), + CommitSuccessReason::FromBot => write!(f, "skipped: sign-off not required in bot commit"), + CommitSuccessReason::IsMerge => write!(f, "skipped: sign-off not required in merge commit"), CommitSuccessReason::ValidSignOff => write!(f, "valid sign-off found"), + CommitSuccessReason::ValidSignOffInRemediationCommit => { + write!(f, "valid sign-off found in remediation commit") + } } } } @@ -87,6 +92,9 @@ pub(crate) fn check(input: &CheckInput) -> CheckOutput { num_commits_with_errors: 0, }; + // Get remediations from all commits + let remediations = get_remediations(&input.config.allow_remediation_commits, &input.commits); + // Check each commit for commit in &input.commits { let mut commit_output = CommitCheckOutput::new(commit.clone()); @@ -115,14 +123,21 @@ pub(crate) fn check(input: &CheckInput) -> CheckOutput { } // Check if any of the sign-offs matches the author's or committer's email - if emails_are_valid && !signoffs.is_empty() && !signoffs_match(&signoffs, commit) { - commit_output.errors.push(CommitError::SignOffMismatch); + if emails_are_valid && !signoffs.is_empty() { + if signoffs_match(&signoffs, commit) { + commit_output.success_reason = Some(CommitSuccessReason::ValidSignOff); + } else { + commit_output.errors.push(CommitError::SignOffMismatch); + } } - // Track commit - if commit_output.errors.is_empty() { - commit_output.success_reason = Some(CommitSuccessReason::ValidSignOff); + // Check if the sign-off is present in a remediation commit + if commit_output.success_reason.is_none() && remediations_match(&remediations, commit) { + commit_output.errors.clear(); + commit_output.success_reason = Some(CommitSuccessReason::ValidSignOffInRemediationCommit); } + + // Track commit output.commits.push(commit_output); } @@ -186,15 +201,18 @@ static SIGN_OFF: LazyLock<Regex> = LazyLock::new(|| { struct SignOff { name: String, email: String, - kind: SignOffKind, } -/// Sign-off kind. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -enum SignOffKind { - Explicit, - IndividualRemediation, - ThirdPartyRemediation, +impl SignOff { + /// Check if the sign-off matches the provided user (if any). + fn matches_user(&self, user: &Option<GitUser>) -> bool { + if let Some(user) = user { + self.name.to_lowercase() == user.name.to_lowercase() + && self.email.to_lowercase() == user.email.to_lowercase() + } else { + false + } + } } /// Get sign-offs found in the commit message. @@ -205,7 +223,6 @@ fn get_signoffs(commit: &Commit) -> Vec<SignOff> { signoffs.push(SignOff { name: name.to_string(), email: email.to_string(), - kind: SignOffKind::Explicit, }); } @@ -214,21 +231,140 @@ fn get_signoffs(commit: &Commit) -> Vec<SignOff> { /// Check if any of the sign-offs matches the author's or committer's email. fn signoffs_match(signoffs: &[SignOff], commit: &Commit) -> bool { - let signoff_matches_author = |s: &SignOff| { - if let Some(a) = &commit.author { - s.name.to_lowercase() == a.name.to_lowercase() && s.email.to_lowercase() == a.email.to_lowercase() + signoffs + .iter() + .any(|signoff| signoff.matches_user(&commit.author) || signoff.matches_user(&commit.committer)) +} + +/// Individual remediation regular expression. +static INDIVIDUAL_REMEDIATION: LazyLock<Regex> = LazyLock::new(|| { + Regex::new(r"(?mi)^I, (.*) <(.*)>, hereby add my Signed-off-by to this commit: (.*)\s*$") + .expect("expr in INDIVIDUAL_REMEDIATION to be valid") +}); + +/// Third party remediation regular expression. +static THIRD_PARTY_REMEDIATION: LazyLock<Regex> = LazyLock::new(|| { + Regex::new(r"(?mi)^On behalf of (.*) <(.*)>, I, (.*) <(.*)>, hereby add my Signed-off-by to this commit: (.*)\s*$") + .expect("expr in THIRD_PARTY_REMEDIATION to be valid") +}); + +/// Remediation details. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +struct Remediation { + pub declarant: GitUser, + pub target_sha: String, +} + +impl Remediation { + /// Create a new remediation. + fn new( + declarant_name: &str, + declarant_email: &str, + representative_name: Option<&str>, + representative_email: Option<&str>, + target_sha: &str, + commit: &Commit, + ) -> Result<Self> { + // Prepare declarant and representative + let declarant = GitUser { + name: declarant_name.to_string(), + email: declarant_email.to_string(), + ..Default::default() + }; + let representative = 'representative: { + let Some(name) = representative_name else { + break 'representative None; + }; + let Some(email) = representative_email else { + break 'representative None; + }; + Some(GitUser { + name: name.to_string(), + email: email.to_string(), + ..Default::default() + }) + }; + + // If the representative is provided, it must match the author or committer + if let Some(representative) = &representative { + if !representative.matches(&commit.author) && !representative.matches(&commit.committer) { + bail!("representative must match the author or committer"); + } } else { - false + // Otherwise, the declarant must match the author or committer + if !declarant.matches(&commit.author) && !declarant.matches(&commit.committer) { + bail!("declarant must match the author or committer"); + } } - }; - let signoff_matches_committer = |s: &SignOff| { - if let Some(c) = &commit.committer { - s.name.to_lowercase() == c.name.to_lowercase() && s.email.to_lowercase() == c.email.to_lowercase() - } else { - false + // Create remediation and return it + Ok(Remediation { + declarant, + target_sha: target_sha.to_string(), + }) + } + + /// Check if the remediation matches the provided commit. + fn matches_commit(&self, commit: &Commit) -> bool { + if self.target_sha != commit.sha { + return false; } + self.declarant.matches(&commit.author) || self.declarant.matches(&commit.committer) + } +} + +/// Get remediations found in the list of commits provided. +fn get_remediations( + allow_remediation_commits: &Option<ConfigAllowRemediationCommits>, + commits: &[Commit], +) -> Vec<Remediation> { + let mut remediations = Vec::new(); + + // Nothing to do if this feature isn't enabled in the config + let Some(allow_remediation_commits) = allow_remediation_commits else { + return remediations; }; - signoffs.iter().any(|s| signoff_matches_author(s) || signoff_matches_committer(s)) + // Collect remediations from commits + for commit in commits { + // Collect individual remediations if this feature is enabled + if allow_remediation_commits.individual.unwrap_or(false) { + let captures = INDIVIDUAL_REMEDIATION.captures_iter(&commit.message).map(|c| c.extract()); + for (_, [declarant_name, declarant_email, target_sha]) in captures { + if let Ok(remediation) = + Remediation::new(declarant_name, declarant_email, None, None, target_sha, commit) + { + remediations.push(remediation); + } + } + + // Collect third-party remediations if this feature is enabled + if allow_remediation_commits.third_party.unwrap_or(false) { + let captures = THIRD_PARTY_REMEDIATION.captures_iter(&commit.message).map(|c| c.extract()); + for ( + _, + [declarant_name, declarant_email, representative_name, representative_email, target_sha], + ) in captures + { + if let Ok(remediation) = Remediation::new( + declarant_name, + declarant_email, + Some(representative_name), + Some(representative_email), + target_sha, + commit, + ) { + remediations.push(remediation); + } + } + } + } + } + + remediations +} + +/// Check if any of the remediations matches the provided commit. +fn remediations_match(remediations: &[Remediation], commit: &Commit) -> bool { + remediations.iter().any(|remediation| remediation.matches_commit(commit)) } diff --git a/dco2/src/dco/check/tests.rs b/dco2/src/dco/check/tests.rs index f41ca87..5de8412 100644 --- a/dco2/src/dco/check/tests.rs +++ b/dco2/src/dco/check/tests.rs @@ -1,8 +1,9 @@ use crate::{ dco::check::{check, CheckInput, CheckOutput, CommitCheckOutput, CommitError, CommitSuccessReason}, - github::{Commit, GitUser}, + github::{Commit, Config, ConfigAllowRemediationCommits, GitUser}, }; use indoc::indoc; +use pretty_assertions::assert_eq; use std::vec; #[test] @@ -1743,6 +1744,1332 @@ fn two_commits_invalid_signoff_in_first_no_signoff_in_second() { ); } +#[test] +fn two_commits_no_signoff_in_first_valid_remediation_commit_in_second_but_not_enabled_in_config() { + let commit1 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: "Test commit message".to_string(), + sha: "sha1".to_string(), + ..Default::default() + }; + let commit2 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: indoc! {r" + Test commit message + + I, user1 <user1@email.test>, hereby add my Signed-off-by to this commit: sha1 + + Signed-off-by: user1 <user1@email.test> + "} + .to_string(), + ..Default::default() + }; + + let input = CheckInput { + commits: vec![commit1.clone(), commit2.clone()], + config: Default::default(), + head_ref: "main".to_string(), + }; + let output = check(&input); + + assert_eq!( + output, + CheckOutput { + commits: vec![ + CommitCheckOutput { + commit: commit1, + errors: vec![CommitError::SignOffNotFound], + success_reason: None, + }, + CommitCheckOutput { + commit: commit2, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOff), + } + ], + head_ref: "main".to_string(), + num_commits_with_errors: 1, + } + ); +} + +#[test] +fn two_commits_no_signoff_in_first_valid_remediation_commit_matching_author_in_second() { + let commit1 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user2".to_string(), + email: "user2@email.test".to_string(), + ..Default::default() + }), + message: "Test commit message".to_string(), + sha: "sha1".to_string(), + ..Default::default() + }; + let commit2 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: indoc! {r" + Test commit message + + I, user1 <user1@email.test>, hereby add my Signed-off-by to this commit: sha1 + + Signed-off-by: user1 <user1@email.test> + "} + .to_string(), + ..Default::default() + }; + + let input = CheckInput { + commits: vec![commit1.clone(), commit2.clone()], + config: Config { + allow_remediation_commits: Some(ConfigAllowRemediationCommits { + individual: Some(true), + ..Default::default() + }), + ..Default::default() + }, + head_ref: "main".to_string(), + }; + let output = check(&input); + + assert_eq!( + output, + CheckOutput { + commits: vec![ + CommitCheckOutput { + commit: commit1, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOffInRemediationCommit), + }, + CommitCheckOutput { + commit: commit2, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOff), + } + ], + head_ref: "main".to_string(), + num_commits_with_errors: 0, + } + ); +} + +#[test] +fn two_commits_no_signoff_in_first_valid_remediation_commit_matching_committer_in_second() { + let commit1 = Commit { + author: Some(GitUser { + name: "user2".to_string(), + email: "user2@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: "Test commit message".to_string(), + sha: "sha1".to_string(), + ..Default::default() + }; + let commit2 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: indoc! {r" + Test commit message + + I, user1 <user1@email.test>, hereby add my Signed-off-by to this commit: sha1 + + Signed-off-by: user1 <user1@email.test> + "} + .to_string(), + ..Default::default() + }; + + let input = CheckInput { + commits: vec![commit1.clone(), commit2.clone()], + config: Config { + allow_remediation_commits: Some(ConfigAllowRemediationCommits { + individual: Some(true), + ..Default::default() + }), + ..Default::default() + }, + head_ref: "main".to_string(), + }; + let output = check(&input); + + assert_eq!( + output, + CheckOutput { + commits: vec![ + CommitCheckOutput { + commit: commit1, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOffInRemediationCommit), + }, + CommitCheckOutput { + commit: commit2, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOff), + } + ], + head_ref: "main".to_string(), + num_commits_with_errors: 0, + } + ); +} + +#[test] +fn two_commits_invalid_signoff_incorrect_name_in_first_valid_remediation_commit_in_second() { + let commit1 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: indoc! {r" + Test commit message + + Signed-off-by: userx <user1@email.test> + "} + .to_string(), + sha: "sha1".to_string(), + ..Default::default() + }; + let commit2 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: indoc! {r" + Test commit message + + I, user1 <user1@email.test>, hereby add my Signed-off-by to this commit: sha1 + + Signed-off-by: user1 <user1@email.test> + "} + .to_string(), + ..Default::default() + }; + + let input = CheckInput { + commits: vec![commit1.clone(), commit2.clone()], + config: Config { + allow_remediation_commits: Some(ConfigAllowRemediationCommits { + individual: Some(true), + ..Default::default() + }), + ..Default::default() + }, + head_ref: "main".to_string(), + }; + let output = check(&input); + + assert_eq!( + output, + CheckOutput { + commits: vec![ + CommitCheckOutput { + commit: commit1, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOffInRemediationCommit), + }, + CommitCheckOutput { + commit: commit2, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOff), + } + ], + head_ref: "main".to_string(), + num_commits_with_errors: 0, + } + ); +} + +#[test] +fn two_commits_invalid_signoff_incorrect_email_in_first_valid_remediation_commit_in_second() { + let commit1 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: indoc! {r" + Test commit message + + Signed-off-by: user1 <userx@email.test> + "} + .to_string(), + sha: "sha1".to_string(), + ..Default::default() + }; + let commit2 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: indoc! {r" + Test commit message + + I, user1 <user1@email.test>, hereby add my Signed-off-by to this commit: sha1 + + Signed-off-by: user1 <user1@email.test> + "} + .to_string(), + ..Default::default() + }; + + let input = CheckInput { + commits: vec![commit1.clone(), commit2.clone()], + config: Config { + allow_remediation_commits: Some(ConfigAllowRemediationCommits { + individual: Some(true), + ..Default::default() + }), + ..Default::default() + }, + head_ref: "main".to_string(), + }; + let output = check(&input); + + assert_eq!( + output, + CheckOutput { + commits: vec![ + CommitCheckOutput { + commit: commit1, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOffInRemediationCommit), + }, + CommitCheckOutput { + commit: commit2, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOff), + } + ], + head_ref: "main".to_string(), + num_commits_with_errors: 0, + } + ); +} + +#[test] +fn two_commits_valid_signoff_in_first_redundant_remediation_commit_in_second() { + let commit1 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: indoc! {r" + Test commit message + + Signed-off-by: user1 <user1@email.test> + "} + .to_string(), + sha: "sha1".to_string(), + ..Default::default() + }; + let commit2 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: indoc! {r" + Test commit message + + I, user1 <user1@email.test>, hereby add my Signed-off-by to this commit: sha1 + + Signed-off-by: user1 <user1@email.test> + "} + .to_string(), + ..Default::default() + }; + + let input = CheckInput { + commits: vec![commit1.clone(), commit2.clone()], + config: Config { + allow_remediation_commits: Some(ConfigAllowRemediationCommits { + individual: Some(true), + ..Default::default() + }), + ..Default::default() + }, + head_ref: "main".to_string(), + }; + let output = check(&input); + + assert_eq!( + output, + CheckOutput { + commits: vec![ + CommitCheckOutput { + commit: commit1, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOff), + }, + CommitCheckOutput { + commit: commit2, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOff), + } + ], + head_ref: "main".to_string(), + num_commits_with_errors: 0, + } + ); +} + +#[test] +fn two_commits_valid_signoff_in_first_remediation_commit_non_existent_sha_in_second() { + let commit1 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: indoc! {r" + Test commit message + + Signed-off-by: user1 <user1@email.test> + "} + .to_string(), + sha: "sha1".to_string(), + ..Default::default() + }; + let commit2 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: indoc! {r" + Test commit message + + I, user1 <user1@email.test>, hereby add my Signed-off-by to this commit: non-existent + + Signed-off-by: user1 <user1@email.test> + "} + .to_string(), + ..Default::default() + }; + + let input = CheckInput { + commits: vec![commit1.clone(), commit2.clone()], + config: Config { + allow_remediation_commits: Some(ConfigAllowRemediationCommits { + individual: Some(true), + ..Default::default() + }), + ..Default::default() + }, + head_ref: "main".to_string(), + }; + let output = check(&input); + + assert_eq!( + output, + CheckOutput { + commits: vec![ + CommitCheckOutput { + commit: commit1, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOff), + }, + CommitCheckOutput { + commit: commit2, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOff), + } + ], + head_ref: "main".to_string(), + num_commits_with_errors: 0, + } + ); +} + +#[test] +fn two_commits_no_signoff_in_first_remediation_commit_no_signoff_in_second() { + let commit1 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: "Test commit message".to_string(), + sha: "sha1".to_string(), + ..Default::default() + }; + let commit2 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: indoc! {r" + Test commit message + + I, user1 <user1@email.test>, hereby add my Signed-off-by to this commit: sha1 + "} + .to_string(), + ..Default::default() + }; + + let input = CheckInput { + commits: vec![commit1.clone(), commit2.clone()], + config: Config { + allow_remediation_commits: Some(ConfigAllowRemediationCommits { + individual: Some(true), + ..Default::default() + }), + ..Default::default() + }, + head_ref: "main".to_string(), + }; + let output = check(&input); + + assert_eq!( + output, + CheckOutput { + commits: vec![ + CommitCheckOutput { + commit: commit1, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOffInRemediationCommit), + }, + CommitCheckOutput { + commit: commit2, + errors: vec![CommitError::SignOffNotFound], + success_reason: None, + } + ], + head_ref: "main".to_string(), + num_commits_with_errors: 1, + } + ); +} + +#[test] +fn two_commits_no_signoff_in_first_remediation_commit_different_name_in_second() { + let commit1 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: "Test commit message".to_string(), + sha: "sha1".to_string(), + ..Default::default() + }; + let commit2 = Commit { + author: Some(GitUser { + name: "userx".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "userx".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: indoc! {r" + Test commit message + + I, userx <user1@email.test>, hereby add my Signed-off-by to this commit: sha1 + + Signed-off-by: userx <user1@email.test> + "} + .to_string(), + ..Default::default() + }; + + let input = CheckInput { + commits: vec![commit1.clone(), commit2.clone()], + config: Config { + allow_remediation_commits: Some(ConfigAllowRemediationCommits { + individual: Some(true), + ..Default::default() + }), + ..Default::default() + }, + head_ref: "main".to_string(), + }; + let output = check(&input); + + assert_eq!( + output, + CheckOutput { + commits: vec![ + CommitCheckOutput { + commit: commit1, + errors: vec![CommitError::SignOffNotFound], + success_reason: None, + }, + CommitCheckOutput { + commit: commit2, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOff), + } + ], + head_ref: "main".to_string(), + num_commits_with_errors: 1, + } + ); +} + +#[test] +fn two_commits_no_signoff_in_first_remediation_commit_different_email_in_second() { + let commit1 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: "Test commit message".to_string(), + sha: "sha1".to_string(), + ..Default::default() + }; + let commit2 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "userx@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "userx@email.test".to_string(), + ..Default::default() + }), + message: indoc! {r" + Test commit message + + I, user1 <userx@email.test>, hereby add my Signed-off-by to this commit: sha1 + + Signed-off-by: user1 <userx@email.test> + "} + .to_string(), + ..Default::default() + }; + + let input = CheckInput { + commits: vec![commit1.clone(), commit2.clone()], + config: Config { + allow_remediation_commits: Some(ConfigAllowRemediationCommits { + individual: Some(true), + ..Default::default() + }), + ..Default::default() + }, + head_ref: "main".to_string(), + }; + let output = check(&input); + + assert_eq!( + output, + CheckOutput { + commits: vec![ + CommitCheckOutput { + commit: commit1, + errors: vec![CommitError::SignOffNotFound], + success_reason: None, + }, + CommitCheckOutput { + commit: commit2, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOff), + } + ], + head_ref: "main".to_string(), + num_commits_with_errors: 1, + } + ); +} + +#[test] +fn two_commits_no_signoff_in_first_remediation_commit_different_name_and_email_in_second() { + let commit1 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: "Test commit message".to_string(), + sha: "sha1".to_string(), + ..Default::default() + }; + let commit2 = Commit { + author: Some(GitUser { + name: "userx".to_string(), + email: "userx@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "userx".to_string(), + email: "userx@email.test".to_string(), + ..Default::default() + }), + message: indoc! {r" + Test commit message + + I, userx <userx@email.test>, hereby add my Signed-off-by to this commit: sha1 + + Signed-off-by: userx <userx@email.test> + "} + .to_string(), + ..Default::default() + }; + + let input = CheckInput { + commits: vec![commit1.clone(), commit2.clone()], + config: Config { + allow_remediation_commits: Some(ConfigAllowRemediationCommits { + individual: Some(true), + ..Default::default() + }), + ..Default::default() + }, + head_ref: "main".to_string(), + }; + let output = check(&input); + + assert_eq!( + output, + CheckOutput { + commits: vec![ + CommitCheckOutput { + commit: commit1, + errors: vec![CommitError::SignOffNotFound], + success_reason: None, + }, + CommitCheckOutput { + commit: commit2, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOff), + } + ], + head_ref: "main".to_string(), + num_commits_with_errors: 1, + } + ); +} + +#[test] +fn two_commits_no_signoff_in_first_remediation_commit_different_name_in_signoff_in_second() { + let commit1 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: "Test commit message".to_string(), + sha: "sha1".to_string(), + ..Default::default() + }; + let commit2 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: indoc! {r" + Test commit message + + I, user1 <user1@email.test>, hereby add my Signed-off-by to this commit: sha1 + + Signed-off-by: userx <user1@email.test> + "} + .to_string(), + ..Default::default() + }; + + let input = CheckInput { + commits: vec![commit1.clone(), commit2.clone()], + config: Config { + allow_remediation_commits: Some(ConfigAllowRemediationCommits { + individual: Some(true), + ..Default::default() + }), + ..Default::default() + }, + head_ref: "main".to_string(), + }; + let output = check(&input); + + assert_eq!( + output, + CheckOutput { + commits: vec![ + CommitCheckOutput { + commit: commit1, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOffInRemediationCommit), + }, + CommitCheckOutput { + commit: commit2, + errors: vec![CommitError::SignOffMismatch], + success_reason: None, + } + ], + head_ref: "main".to_string(), + num_commits_with_errors: 1, + } + ); +} + +#[test] +fn two_commits_no_signoff_in_first_remediation_commit_different_email_in_signoff_in_second() { + let commit1 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: "Test commit message".to_string(), + sha: "sha1".to_string(), + ..Default::default() + }; + let commit2 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: indoc! {r" + Test commit message + + I, user1 <user1@email.test>, hereby add my Signed-off-by to this commit: sha1 + + Signed-off-by: user1 <userx@email.test> + "} + .to_string(), + ..Default::default() + }; + + let input = CheckInput { + commits: vec![commit1.clone(), commit2.clone()], + config: Config { + allow_remediation_commits: Some(ConfigAllowRemediationCommits { + individual: Some(true), + ..Default::default() + }), + ..Default::default() + }, + head_ref: "main".to_string(), + }; + let output = check(&input); + + assert_eq!( + output, + CheckOutput { + commits: vec![ + CommitCheckOutput { + commit: commit1, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOffInRemediationCommit), + }, + CommitCheckOutput { + commit: commit2, + errors: vec![CommitError::SignOffMismatch], + success_reason: None, + } + ], + head_ref: "main".to_string(), + num_commits_with_errors: 1, + } + ); +} + +#[test] +fn two_commits_no_signoff_in_first_remediation_commit_different_name_and_email_in_signoff_in_second() { + let commit1 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: "Test commit message".to_string(), + sha: "sha1".to_string(), + ..Default::default() + }; + let commit2 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: indoc! {r" + Test commit message + + I, user1 <user1@email.test>, hereby add my Signed-off-by to this commit: sha1 + + Signed-off-by: userx <userx@email.test> + "} + .to_string(), + ..Default::default() + }; + + let input = CheckInput { + commits: vec![commit1.clone(), commit2.clone()], + config: Config { + allow_remediation_commits: Some(ConfigAllowRemediationCommits { + individual: Some(true), + ..Default::default() + }), + ..Default::default() + }, + head_ref: "main".to_string(), + }; + let output = check(&input); + + assert_eq!( + output, + CheckOutput { + commits: vec![ + CommitCheckOutput { + commit: commit1, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOffInRemediationCommit), + }, + CommitCheckOutput { + commit: commit2, + errors: vec![CommitError::SignOffMismatch], + success_reason: None, + } + ], + head_ref: "main".to_string(), + num_commits_with_errors: 1, + } + ); +} + +#[test] +fn two_commits_no_signoff_in_first_remediation_commit_different_name_in_remediation_in_second() { + let commit1 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: "Test commit message".to_string(), + sha: "sha1".to_string(), + ..Default::default() + }; + let commit2 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: indoc! {r" + Test commit message + + I, userx <user1@email.test>, hereby add my Signed-off-by to this commit: sha1 + + Signed-off-by: user1 <user1@email.test> + "} + .to_string(), + ..Default::default() + }; + + let input = CheckInput { + commits: vec![commit1.clone(), commit2.clone()], + config: Config { + allow_remediation_commits: Some(ConfigAllowRemediationCommits { + individual: Some(true), + ..Default::default() + }), + ..Default::default() + }, + head_ref: "main".to_string(), + }; + let output = check(&input); + + assert_eq!( + output, + CheckOutput { + commits: vec![ + CommitCheckOutput { + commit: commit1, + errors: vec![CommitError::SignOffNotFound], + success_reason: None, + }, + CommitCheckOutput { + commit: commit2, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOff), + } + ], + head_ref: "main".to_string(), + num_commits_with_errors: 1, + } + ); +} + +#[test] +fn two_commits_no_signoff_in_first_remediation_commit_different_email_in_remediation_in_second() { + let commit1 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: "Test commit message".to_string(), + sha: "sha1".to_string(), + ..Default::default() + }; + let commit2 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: indoc! {r" + Test commit message + + I, user1 <userx@email.test>, hereby add my Signed-off-by to this commit: sha1 + + Signed-off-by: user1 <user1@email.test> + "} + .to_string(), + ..Default::default() + }; + + let input = CheckInput { + commits: vec![commit1.clone(), commit2.clone()], + config: Config { + allow_remediation_commits: Some(ConfigAllowRemediationCommits { + individual: Some(true), + ..Default::default() + }), + ..Default::default() + }, + head_ref: "main".to_string(), + }; + let output = check(&input); + + assert_eq!( + output, + CheckOutput { + commits: vec![ + CommitCheckOutput { + commit: commit1, + errors: vec![CommitError::SignOffNotFound], + success_reason: None, + }, + CommitCheckOutput { + commit: commit2, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOff), + } + ], + head_ref: "main".to_string(), + num_commits_with_errors: 1, + } + ); +} + +#[test] +fn two_commits_no_signoff_in_first_remediation_commit_different_name_and_email_in_remediation_in_second() { + let commit1 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: "Test commit message".to_string(), + sha: "sha1".to_string(), + ..Default::default() + }; + let commit2 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: indoc! {r" + Test commit message + + I, userx <userx@email.test>, hereby add my Signed-off-by to this commit: sha1 + + Signed-off-by: user1 <user1@email.test> + "} + .to_string(), + ..Default::default() + }; + + let input = CheckInput { + commits: vec![commit1.clone(), commit2.clone()], + config: Config { + allow_remediation_commits: Some(ConfigAllowRemediationCommits { + individual: Some(true), + ..Default::default() + }), + ..Default::default() + }, + head_ref: "main".to_string(), + }; + let output = check(&input); + + assert_eq!( + output, + CheckOutput { + commits: vec![ + CommitCheckOutput { + commit: commit1, + errors: vec![CommitError::SignOffNotFound], + success_reason: None, + }, + CommitCheckOutput { + commit: commit2, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOff), + } + ], + head_ref: "main".to_string(), + num_commits_with_errors: 1, + } + ); +} + +#[test] +fn two_commits_no_signoff_in_first_remediation_commit_different_sha_in_second() { + let commit1 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: "Test commit message".to_string(), + sha: "sha1".to_string(), + ..Default::default() + }; + let commit2 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: indoc! {r" + Test commit message + + I, user1 <user1@email.test>, hereby add my Signed-off-by to this commit: sha2 + + Signed-off-by: user1 <user1@email.test> + "} + .to_string(), + ..Default::default() + }; + + let input = CheckInput { + commits: vec![commit1.clone(), commit2.clone()], + config: Config { + allow_remediation_commits: Some(ConfigAllowRemediationCommits { + individual: Some(true), + ..Default::default() + }), + ..Default::default() + }, + head_ref: "main".to_string(), + }; + let output = check(&input); + + assert_eq!( + output, + CheckOutput { + commits: vec![ + CommitCheckOutput { + commit: commit1, + errors: vec![CommitError::SignOffNotFound], + success_reason: None, + }, + CommitCheckOutput { + commit: commit2, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOff), + } + ], + head_ref: "main".to_string(), + num_commits_with_errors: 1, + } + ); +} + #[test] fn three_commits_valid_signoff_in_all() { let commit1 = Commit { @@ -2032,3 +3359,196 @@ fn three_commits_valid_signoff_first_invalid_signoff_second_valid_signoff_third( } ); } + +#[test] +fn three_commits_no_signoff_in_first_remediation_commit_without_signoff_in_second_valid_remediation_commit_in_third( +) { + let commit1 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: "Test commit message".to_string(), + sha: "sha1".to_string(), + ..Default::default() + }; + let commit2 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: indoc! {r" + Test commit message + + I, user1 <user1@email.test>, hereby add my Signed-off-by to this commit: sha1 + "} + .to_string(), + sha: "sha2".to_string(), + ..Default::default() + }; + let commit3 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: indoc! {r" + Test commit message + + I, user1 <user1@email.test>, hereby add my Signed-off-by to this commit: sha2 + + Signed-off-by: user1 <user1@email.test> + "} + .to_string(), + ..Default::default() + }; + + let input = CheckInput { + commits: vec![commit1.clone(), commit2.clone(), commit3.clone()], + config: Config { + allow_remediation_commits: Some(ConfigAllowRemediationCommits { + individual: Some(true), + ..Default::default() + }), + ..Default::default() + }, + head_ref: "main".to_string(), + }; + let output = check(&input); + + assert_eq!( + output, + CheckOutput { + commits: vec![ + CommitCheckOutput { + commit: commit1, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOffInRemediationCommit), + }, + CommitCheckOutput { + commit: commit2, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOffInRemediationCommit), + }, + CommitCheckOutput { + commit: commit3, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOff), + } + ], + head_ref: "main".to_string(), + num_commits_with_errors: 0, + } + ); +} + +#[test] +fn three_commits_no_signoff_in_first_no_signoff_in_second_valid_remediation_commit_for_both_in_third() { + let commit1 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: "Test commit message".to_string(), + sha: "sha1".to_string(), + ..Default::default() + }; + let commit2 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: "Test commit message".to_string(), + sha: "sha2".to_string(), + ..Default::default() + }; + let commit3 = Commit { + author: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + committer: Some(GitUser { + name: "user1".to_string(), + email: "user1@email.test".to_string(), + ..Default::default() + }), + message: indoc! {r" + Test commit message + + I, user1 <user1@email.test>, hereby add my Signed-off-by to this commit: sha1 + I, user1 <user1@email.test>, hereby add my Signed-off-by to this commit: sha2 + + Signed-off-by: user1 <user1@email.test> + "} + .to_string(), + ..Default::default() + }; + + let input = CheckInput { + commits: vec![commit1.clone(), commit2.clone(), commit3.clone()], + config: Config { + allow_remediation_commits: Some(ConfigAllowRemediationCommits { + individual: Some(true), + ..Default::default() + }), + ..Default::default() + }, + head_ref: "main".to_string(), + }; + let output = check(&input); + + assert_eq!( + output, + CheckOutput { + commits: vec![ + CommitCheckOutput { + commit: commit1, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOffInRemediationCommit), + }, + CommitCheckOutput { + commit: commit2, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOffInRemediationCommit), + }, + CommitCheckOutput { + commit: commit3, + errors: vec![], + success_reason: Some(CommitSuccessReason::ValidSignOff), + } + ], + head_ref: "main".to_string(), + num_commits_with_errors: 0, + } + ); +} diff --git a/dco2/src/github/client.rs b/dco2/src/github/client.rs index 7e519c4..e8cec59 100644 --- a/dco2/src/github/client.rs +++ b/dco2/src/github/client.rs @@ -357,8 +357,8 @@ impl From<octorust::types::CommitDataType> for Commit { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all(deserialize = "camelCase"))] pub struct Config { - allow_remediation_commits: Option<ConfigAllowRemediationCommits>, - require: Option<ConfigRequire>, + pub allow_remediation_commits: Option<ConfigAllowRemediationCommits>, + pub require: Option<ConfigRequire>, } impl Default for Config { @@ -376,25 +376,25 @@ impl Default for Config { } /// Allow remediation commits section of the configuration. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] #[serde(rename_all(deserialize = "camelCase"))] pub struct ConfigAllowRemediationCommits { /// Indicates whether individual remediation commits are allowed or not. /// (default: false) - individual: Option<bool>, + pub individual: Option<bool>, /// Indicates whether third party remediation commits are allowed or not. /// (default: false) - third_party: Option<bool>, + pub third_party: Option<bool>, } /// Require section of the configuration. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] #[serde(rename_all(deserialize = "camelCase"))] pub struct ConfigRequire { /// Indicates whether members are required to sign-off or not. /// (default: true) - members: Option<bool>, + pub members: Option<bool>, } /// Git user information. @@ -405,6 +405,18 @@ pub struct GitUser { pub is_bot: bool, } +impl GitUser { + /// Check if the user matches the provided user (if any). + pub fn matches(&self, user: &Option<GitUser>) -> bool { + if let Some(user) = user { + self.name.to_lowercase() == user.name.to_lowercase() + && self.email.to_lowercase() == user.email.to_lowercase() + } else { + false + } + } +} + /// Input used to create a new check run. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct NewCheckRunInput {