Skip to content

Commit ec68766

Browse files
authored
Merge pull request #1897 from Urgau/relink-handler
Automatically fixup linked issues in subtree repository
2 parents ca76b05 + 0e1f413 commit ec68766

File tree

4 files changed

+131
-2
lines changed

4 files changed

+131
-2
lines changed

src/config.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ pub(crate) struct Config {
4646
pub(crate) merge_conflicts: Option<MergeConflictConfig>,
4747
pub(crate) bot_pull_requests: Option<BotPullRequests>,
4848
pub(crate) rendered_link: Option<RenderedLinkConfig>,
49+
pub(crate) canonicalize_issue_links: Option<CanonicalizeIssueLinksConfig>,
4950
}
5051

5152
#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
@@ -414,6 +415,11 @@ pub(crate) struct RenderedLinkConfig {
414415
pub(crate) trigger_files: Vec<String>,
415416
}
416417

418+
#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
419+
#[serde(rename_all = "kebab-case")]
420+
#[serde(deny_unknown_fields)]
421+
pub(crate) struct CanonicalizeIssueLinksConfig {}
422+
417423
fn get_cached_config(repo: &str) -> Option<Result<Arc<Config>, ConfigurationError>> {
418424
let cache = CONFIG_CACHE.read().unwrap();
419425
cache.get(repo).and_then(|(config, fetch_time)| {
@@ -535,6 +541,8 @@ mod tests {
535541
536542
[shortcut]
537543
544+
[canonicalize-issue-links]
545+
538546
[rendered-link]
539547
trigger-files = ["posts/"]
540548
"#;
@@ -598,7 +606,8 @@ mod tests {
598606
bot_pull_requests: None,
599607
rendered_link: Some(RenderedLinkConfig {
600608
trigger_files: vec!["posts/".to_string()]
601-
})
609+
}),
610+
canonicalize_issue_links: Some(CanonicalizeIssueLinksConfig {}),
602611
}
603612
);
604613
}
@@ -662,6 +671,7 @@ mod tests {
662671
merge_conflicts: None,
663672
bot_pull_requests: None,
664673
rendered_link: None,
674+
canonicalize_issue_links: None
665675
}
666676
);
667677
}

src/github.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,7 @@ impl IssueRepository {
522522
)
523523
}
524524

525-
fn full_repo_name(&self) -> String {
525+
pub(crate) fn full_repo_name(&self) -> String {
526526
format!("{}/{}", self.organization, self.repository)
527527
}
528528

src/handlers.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ impl fmt::Display for HandlerError {
2727
mod assign;
2828
mod autolabel;
2929
mod bot_pull_requests;
30+
mod canonicalize_issue_links;
3031
mod check_commits;
3132
mod close;
3233
pub mod docs_update;
@@ -224,6 +225,7 @@ macro_rules! issue_handlers {
224225
issue_handlers! {
225226
assign,
226227
autolabel,
228+
canonicalize_issue_links,
227229
major_change,
228230
mentions,
229231
no_merges,
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
//! This handler is used to canonicalize linked GitHub issues into their long form
2+
//! so that when pulling subtree into the main repository we don't accidentaly
3+
//! close issues in the wrong repository.
4+
//!
5+
//! Example: `Fixes #123` (in rust-lang/clippy) would now become `Fixes rust-lang/clippy#123`
6+
7+
use std::borrow::Cow;
8+
use std::sync::LazyLock;
9+
10+
use regex::Regex;
11+
12+
use crate::{
13+
config::CanonicalizeIssueLinksConfig,
14+
github::{IssuesAction, IssuesEvent},
15+
handlers::Context,
16+
};
17+
18+
// Taken from https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue?quot#linking-a-pull-request-to-an-issue-using-a-keyword
19+
static LINKED_RE: LazyLock<Regex> = LazyLock::new(|| {
20+
Regex::new("(?i)(?P<action>close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)(?P<spaces>:? +)(?P<issue>#[0-9]+)")
21+
.unwrap()
22+
});
23+
24+
pub(super) struct CanonicalizeIssueLinksInput {}
25+
26+
pub(super) async fn parse_input(
27+
_ctx: &Context,
28+
event: &IssuesEvent,
29+
config: Option<&CanonicalizeIssueLinksConfig>,
30+
) -> Result<Option<CanonicalizeIssueLinksInput>, String> {
31+
if !event.issue.is_pr() {
32+
return Ok(None);
33+
}
34+
35+
if !matches!(
36+
event.action,
37+
IssuesAction::Opened | IssuesAction::Reopened | IssuesAction::Edited
38+
) {
39+
return Ok(None);
40+
}
41+
42+
// Require a `[canonicalize-issue-links]` configuration block to enable the handler.
43+
if config.is_none() {
44+
return Ok(None);
45+
};
46+
47+
Ok(Some(CanonicalizeIssueLinksInput {}))
48+
}
49+
50+
pub(super) async fn handle_input(
51+
ctx: &Context,
52+
_config: &CanonicalizeIssueLinksConfig,
53+
e: &IssuesEvent,
54+
_input: CanonicalizeIssueLinksInput,
55+
) -> anyhow::Result<()> {
56+
let full_repo_name = e.issue.repository().full_repo_name();
57+
58+
let new_body = fix_linked_issues(&e.issue.body, full_repo_name.as_str());
59+
60+
if e.issue.body != new_body {
61+
e.issue.edit_body(&ctx.github, &new_body).await?;
62+
}
63+
64+
Ok(())
65+
}
66+
67+
fn fix_linked_issues<'a>(body: &'a str, full_repo_name: &str) -> Cow<'a, str> {
68+
let replace_by = format!("${{action}}${{spaces}}{full_repo_name}${{issue}}");
69+
LINKED_RE.replace_all(body, replace_by)
70+
}
71+
72+
#[test]
73+
fn fixed_body() {
74+
let full_repo_name = "rust-lang/rust";
75+
76+
let body = r#"
77+
This is a PR.
78+
79+
Fix #123
80+
fixed #456
81+
Fixes #7895
82+
Closes: #987
83+
resolves: #655
84+
Resolves #00000 Closes #888
85+
"#;
86+
87+
let fixed_body = r#"
88+
This is a PR.
89+
90+
Fix rust-lang/rust#123
91+
fixed rust-lang/rust#456
92+
Fixes rust-lang/rust#7895
93+
Closes: rust-lang/rust#987
94+
resolves: rust-lang/rust#655
95+
Resolves rust-lang/rust#00000 Closes rust-lang/rust#888
96+
"#;
97+
98+
let new_body = fix_linked_issues(body, full_repo_name);
99+
assert_eq!(new_body, fixed_body);
100+
}
101+
102+
#[test]
103+
fn untouched_body() {
104+
let full_repo_name = "rust-lang/rust";
105+
106+
let body = r#"
107+
This is a PR.
108+
109+
Fix rust-lang#123
110+
Fixesd #7895
111+
Resolves #abgt
112+
Resolves: #abgt
113+
"#;
114+
115+
let new_body = fix_linked_issues(body, full_repo_name);
116+
assert_eq!(new_body, body);
117+
}

0 commit comments

Comments
 (0)