Skip to content

Commit fcc12eb

Browse files
authored
Merge pull request #2175 from Urgau/mentions-uncanonicalized-in-commits
Add support for only checking uncanonicalized issue links in commits
2 parents 935eeaf + e7b10b3 commit fcc12eb

File tree

2 files changed

+144
-17
lines changed

2 files changed

+144
-17
lines changed

src/config.rs

Lines changed: 73 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -521,8 +521,62 @@ pub(crate) struct RenderedLinkConfig {
521521
#[serde(rename_all = "kebab-case")]
522522
#[serde(deny_unknown_fields)]
523523
pub(crate) struct IssueLinksConfig {
524-
#[serde(default = "default_true")]
525-
pub(crate) check_commits: bool,
524+
#[serde(default)]
525+
pub(crate) check_commits: IssueLinksCheckCommitsConfig,
526+
}
527+
528+
#[derive(PartialEq, Eq, Debug)]
529+
pub(crate) enum IssueLinksCheckCommitsConfig {
530+
/// No checking of commits
531+
Off,
532+
/// Only check for uncanonicalized issue links in commits
533+
Uncanonicalized,
534+
/// Check for all issue links in commits
535+
All,
536+
}
537+
538+
impl Default for IssueLinksCheckCommitsConfig {
539+
fn default() -> Self {
540+
IssueLinksCheckCommitsConfig::All
541+
}
542+
}
543+
544+
impl<'de> serde::Deserialize<'de> for IssueLinksCheckCommitsConfig {
545+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
546+
where
547+
D: serde::Deserializer<'de>,
548+
{
549+
struct CheckCommitsVisitor;
550+
551+
impl<'de> serde::de::Visitor<'de> for CheckCommitsVisitor {
552+
type Value = IssueLinksCheckCommitsConfig;
553+
554+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
555+
formatter.write_str("a bool or the string \"uncanonicalized\"")
556+
}
557+
558+
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E> {
559+
Ok(if v {
560+
IssueLinksCheckCommitsConfig::All
561+
} else {
562+
IssueLinksCheckCommitsConfig::Off
563+
})
564+
}
565+
566+
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
567+
where
568+
E: serde::de::Error,
569+
{
570+
if v == "uncanonicalized" {
571+
Ok(IssueLinksCheckCommitsConfig::Uncanonicalized)
572+
} else {
573+
Err(E::custom("expected \"uncanonicalized\""))
574+
}
575+
}
576+
}
577+
578+
deserializer.deserialize_any(CheckCommitsVisitor)
579+
}
526580
}
527581

528582
#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
@@ -545,11 +599,6 @@ pub(crate) struct BehindUpstreamConfig {
545599
pub(crate) days_threshold: Option<usize>,
546600
}
547601

548-
#[inline]
549-
fn default_true() -> bool {
550-
true
551-
}
552-
553602
#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
554603
pub(crate) struct BackportConfig {
555604
// Config identifier -> labels
@@ -841,7 +890,7 @@ mod tests {
841890
exclude_files: vec![],
842891
}),
843892
issue_links: Some(IssueLinksConfig {
844-
check_commits: true,
893+
check_commits: IssueLinksCheckCommitsConfig::All,
845894
}),
846895
no_mentions: Some(NoMentionsConfig {
847896
exclude_titles: vec!["subtree update".into()],
@@ -936,7 +985,7 @@ mod tests {
936985
bot_pull_requests: None,
937986
rendered_link: None,
938987
issue_links: Some(IssueLinksConfig {
939-
check_commits: false,
988+
check_commits: IssueLinksCheckCommitsConfig::Off,
940989
}),
941990
no_mentions: None,
942991
behind_upstream: Some(BehindUpstreamConfig {
@@ -976,4 +1025,19 @@ mod tests {
9761025
})
9771026
);
9781027
}
1028+
1029+
#[test]
1030+
fn issue_links_uncanonicalized() {
1031+
let config = r#"
1032+
[issue-links]
1033+
check-commits = "uncanonicalized"
1034+
"#;
1035+
let config = toml::from_str::<Config>(&config).unwrap();
1036+
assert!(matches!(
1037+
config.issue_links,
1038+
Some(IssueLinksConfig {
1039+
check_commits: IssueLinksCheckCommitsConfig::Uncanonicalized
1040+
})
1041+
));
1042+
}
9791043
}

src/handlers/check_commits/issue_links.rs

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,25 @@ use std::sync::LazyLock;
22

33
use regex::Regex;
44

5-
use crate::{config::IssueLinksConfig, github::GithubCommit};
5+
use crate::{
6+
config::{IssueLinksCheckCommitsConfig, IssueLinksConfig},
7+
github::GithubCommit,
8+
};
69

710
static LINKED_RE: LazyLock<Regex> =
8-
LazyLock::new(|| Regex::new(r"\B([a-zA-Z-_]+/[a-zA-Z-_]+)?(#[0-9]+)\b").unwrap());
11+
LazyLock::new(|| Regex::new(r"\B(?P<org>[a-zA-Z-_]+/[a-zA-Z-_]+)?(#[0-9]+)\b").unwrap());
912

1013
const MERGE_IGNORE_LIST: [&str; 3] = ["Rollup merge of ", "Auto merge of ", "Merge pull request "];
1114

1215
pub(super) fn issue_links_in_commits(
1316
conf: &IssueLinksConfig,
1417
commits: &[GithubCommit],
1518
) -> Option<String> {
16-
if !conf.check_commits {
17-
return None;
18-
}
19+
let does_match = match conf.check_commits {
20+
IssueLinksCheckCommitsConfig::Off => return None,
21+
IssueLinksCheckCommitsConfig::All => has_issue_link,
22+
IssueLinksCheckCommitsConfig::Uncanonicalized => has_uncanonicalized_issue_link,
23+
};
1924

2025
let issue_links_commits = commits
2126
.into_iter()
@@ -24,12 +29,21 @@ pub(super) fn issue_links_in_commits(
2429
.iter()
2530
.any(|i| c.commit.message.starts_with(i))
2631
})
27-
.filter(|c| LINKED_RE.is_match(&c.commit.message))
32+
.filter(|c| does_match(&c.commit.message))
2833
.map(|c| format!("- {}\n", c.sha))
2934
.collect::<String>();
3035

3136
if issue_links_commits.is_empty() {
3237
None
38+
} else if matches!(
39+
conf.check_commits,
40+
IssueLinksCheckCommitsConfig::Uncanonicalized
41+
) {
42+
Some(format!(
43+
r"There are uncanonicalized issue links (such as `#123`) in the commit messages of the following commits.
44+
*Please add the organization and repository before the issue number (like so `rust-lang/rust#123`) to avoid issues with subtree.*
45+
{issue_links_commits}",
46+
))
3347
} else {
3448
Some(format!(
3549
r"There are issue links (such as `#123`) in the commit messages of the following commits.
@@ -39,12 +53,23 @@ pub(super) fn issue_links_in_commits(
3953
}
4054
}
4155

56+
fn has_issue_link(text: &str) -> bool {
57+
LINKED_RE.is_match(text)
58+
}
59+
60+
fn has_uncanonicalized_issue_link(text: &str) -> bool {
61+
let Some(caps) = LINKED_RE.captures(text) else {
62+
return false;
63+
};
64+
caps.name("org").is_none()
65+
}
66+
4267
#[test]
4368
fn test_mentions_in_commits() {
4469
use super::dummy_commit_from_body;
4570

4671
let config = IssueLinksConfig {
47-
check_commits: true,
72+
check_commits: IssueLinksCheckCommitsConfig::All,
4873
};
4974

5075
let mut commits = vec![dummy_commit_from_body(
@@ -87,7 +112,7 @@ fn test_mentions_in_commits() {
87112
assert_eq!(
88113
issue_links_in_commits(
89114
&IssueLinksConfig {
90-
check_commits: false,
115+
check_commits: IssueLinksCheckCommitsConfig::Off,
91116
},
92117
&commits
93118
),
@@ -110,3 +135,41 @@ fn test_mentions_in_commits() {
110135
)
111136
);
112137
}
138+
139+
#[test]
140+
fn uncanonicalized() {
141+
use super::dummy_commit_from_body;
142+
143+
let config = IssueLinksConfig {
144+
check_commits: IssueLinksCheckCommitsConfig::Uncanonicalized,
145+
};
146+
147+
let mut commits = vec![dummy_commit_from_body(
148+
"d1992a392617dfb10518c3e56446b6c9efae38b0",
149+
"This is simple without issue links!",
150+
)];
151+
152+
assert_eq!(issue_links_in_commits(&config, &commits), None);
153+
154+
commits.push(dummy_commit_from_body(
155+
"86176475acda9c775f844f5ad2470f05aebd4249",
156+
"Test for canonicalized rust-lang/rust#123",
157+
));
158+
159+
assert_eq!(issue_links_in_commits(&config, &commits), None);
160+
161+
commits.push(dummy_commit_from_body(
162+
"fererfe5acda9c775f844f5ad2470f05aebd4249",
163+
"Test for uncanonicalized #123",
164+
));
165+
166+
assert_eq!(
167+
issue_links_in_commits(&config, &commits),
168+
Some(
169+
r"There are uncanonicalized issue links (such as `#123`) in the commit messages of the following commits.
170+
*Please add the organization and repository before the issue number (like so `rust-lang/rust#123`) to avoid issues with subtree.*
171+
- fererfe5acda9c775f844f5ad2470f05aebd4249
172+
".to_string()
173+
)
174+
);
175+
}

0 commit comments

Comments
 (0)