Skip to content

Commit 8bca1a0

Browse files
committed
Refactor no_merges handler into the check_commits super-handler
1 parent 4fb1228 commit 8bca1a0

File tree

5 files changed

+177
-268
lines changed

5 files changed

+177
-268
lines changed

src/github.rs

+5
Original file line numberDiff line numberDiff line change
@@ -2788,13 +2788,15 @@ impl GithubClient {
27882788
}
27892789

27902790
#[derive(Debug, serde::Deserialize)]
2791+
#[cfg_attr(test, derive(Clone))]
27912792
pub struct GithubCommit {
27922793
pub sha: String,
27932794
pub commit: GithubCommitCommitField,
27942795
pub parents: Vec<Parent>,
27952796
}
27962797

27972798
#[derive(Debug, serde::Deserialize)]
2799+
#[cfg_attr(test, derive(Clone))]
27982800
pub struct GithubCommitCommitField {
27992801
pub author: GitUser,
28002802
pub message: String,
@@ -2810,6 +2812,7 @@ pub struct GitCommit {
28102812
}
28112813

28122814
#[derive(Debug, serde::Deserialize)]
2815+
#[cfg_attr(test, derive(Clone))]
28132816
pub struct GitCommitTree {
28142817
pub sha: String,
28152818
}
@@ -2836,11 +2839,13 @@ pub struct RecentCommit {
28362839
}
28372840

28382841
#[derive(Debug, serde::Deserialize)]
2842+
#[cfg_attr(test, derive(Clone))]
28392843
pub struct GitUser {
28402844
pub date: DateTime<FixedOffset>,
28412845
}
28422846

28432847
#[derive(Debug, serde::Deserialize)]
2848+
#[cfg_attr(test, derive(Clone))]
28442849
pub struct Parent {
28452850
pub sha: String,
28462851
}

src/handlers.rs

-2
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ mod major_change;
3737
mod mentions;
3838
mod merge_conflicts;
3939
mod milestone_prs;
40-
mod no_merges;
4140
mod nominate;
4241
mod note;
4342
mod notification;
@@ -228,7 +227,6 @@ issue_handlers! {
228227
issue_links,
229228
major_change,
230229
mentions,
231-
no_merges,
232230
notify_zulip,
233231
review_requested,
234232
pr_tracking,

src/handlers/check_commits.rs

+10
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use crate::github::GithubCommit;
1616
mod issue_links;
1717
mod modified_submodule;
1818
mod no_mentions;
19+
mod no_merges;
1920
mod non_default_branch;
2021

2122
/// Key for the state in the database
@@ -83,6 +84,15 @@ pub(super) async fn handle(ctx: &Context, event: &Event, config: &Config) -> any
8384
warnings.extend(issue_links::issue_links_in_commits(issue_links, &commits));
8485
}
8586

87+
if let Some(no_merges) = &config.no_merges {
88+
if let Some(warn) =
89+
no_merges::merges_in_commits(&event.issue.title, &event.repository, no_merges, &commits)
90+
{
91+
warnings.push(warn.0);
92+
labels.extend(warn.1);
93+
}
94+
}
95+
8696
handle_warnings_and_labels(ctx, event, warnings, labels).await
8797
}
8898

+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
//! Purpose: When opening a PR, or pushing new changes, check for merge commits
2+
//! and notify the user of our no-merge policy.
3+
4+
use crate::{
5+
config::NoMergesConfig,
6+
github::{GithubCommit, Repository},
7+
};
8+
use std::collections::BTreeSet;
9+
use std::fmt::Write;
10+
11+
pub(super) fn merges_in_commits(
12+
issue_title: &str,
13+
repository: &Repository,
14+
config: &NoMergesConfig,
15+
commits: &Vec<GithubCommit>,
16+
) -> Option<(String, Vec<String>)> {
17+
// Don't trigger if the PR has any of the excluded title segments.
18+
if config
19+
.exclude_titles
20+
.iter()
21+
.any(|s| issue_title.contains(s))
22+
{
23+
return None;
24+
}
25+
26+
let mut merge_commits = BTreeSet::new();
27+
for commit in commits {
28+
if commit.parents.len() > 1 {
29+
merge_commits.insert(&*commit.sha);
30+
}
31+
}
32+
33+
// No merge commits.
34+
if merge_commits.is_empty() {
35+
return None;
36+
}
37+
38+
let message = config
39+
.message
40+
.as_deref()
41+
.unwrap_or(&get_default_message(
42+
&repository.full_name,
43+
&repository.default_branch,
44+
merge_commits.into_iter(),
45+
))
46+
.to_string();
47+
48+
Some((message, config.labels.clone()))
49+
}
50+
51+
fn get_default_message<'a>(
52+
repository_name: &str,
53+
default_branch: &str,
54+
commits: impl IntoIterator<Item = &'a str>,
55+
) -> String {
56+
let mut message = format!(
57+
"
58+
The following commits have merge commits (commits with multiple parents) in your changes. \
59+
We have a [no merge policy](https://rustc-dev-guide.rust-lang.org/git.html#no-merge-policy) \
60+
so these commits will need to be removed for this pull request to be merged.
61+
"
62+
);
63+
64+
for commit in commits {
65+
writeln!(message, " - {commit}").unwrap();
66+
}
67+
68+
writeln!(
69+
message,
70+
"
71+
You can start a rebase with the following commands:
72+
```shell-session
73+
$ # rebase
74+
$ git pull --rebase https://github.com/{repository_name}.git {default_branch}
75+
$ git push --force-with-lease
76+
```"
77+
)
78+
.unwrap();
79+
80+
message
81+
}
82+
83+
#[test]
84+
fn end_to_end() {
85+
use super::dummy_commit_from_body;
86+
use crate::github::Parent;
87+
88+
let config = NoMergesConfig {
89+
exclude_titles: vec!["Subtree update".to_string()],
90+
labels: vec!["merge-commits".to_string()],
91+
message: None,
92+
};
93+
94+
let title = "This a PR!";
95+
let repository = Repository {
96+
full_name: "rust-lang/rust".to_string(),
97+
default_branch: "master".to_string(),
98+
fork: false,
99+
parent: None,
100+
};
101+
102+
let mut commit_with_merge =
103+
dummy_commit_from_body("9cc6dce67c917fe5937e984f58f5003ccbb5c37e", "+ main.rs");
104+
commit_with_merge.parents = vec![
105+
Parent {
106+
sha: "4fb1228e72cf76549e275c38c226c1c2326ca991".to_string(),
107+
},
108+
Parent {
109+
sha: "febd545030008f13541064895ae36e19d929a043".to_string(),
110+
},
111+
];
112+
113+
// contains merges
114+
{
115+
let Some((warning, labels)) = merges_in_commits(
116+
&title,
117+
&repository,
118+
&config,
119+
&vec![
120+
commit_with_merge.clone(),
121+
dummy_commit_from_body("499bdd2d766f98420c66a80a02b7d3ceba4d06ba", "+ nothing.rs"),
122+
],
123+
) else {
124+
unreachable!()
125+
};
126+
assert_eq!(warning, "
127+
The following commits have merge commits (commits with multiple parents) in your changes. We have a [no merge policy](https://rustc-dev-guide.rust-lang.org/git.html#no-merge-policy) so these commits will need to be removed for this pull request to be merged.
128+
- 9cc6dce67c917fe5937e984f58f5003ccbb5c37e
129+
130+
You can start a rebase with the following commands:
131+
```shell-session
132+
$ # rebase
133+
$ git pull --rebase https://github.com/rust-lang/rust.git master
134+
$ git push --force-with-lease
135+
```
136+
");
137+
assert_eq!(labels, vec!["merge-commits"]);
138+
}
139+
140+
// doesn't contains merges
141+
{
142+
let commit = dummy_commit_from_body("67c917fe5937e984f58f5003ccbb5c37e", "+ main.rs");
143+
144+
assert_eq!(
145+
merges_in_commits(&title, &repository, &config, &vec![commit]),
146+
None
147+
);
148+
}
149+
150+
// contains merges, but excluded by title
151+
{
152+
assert_eq!(
153+
merges_in_commits(
154+
"Subtree update of rustc_custom_codegen",
155+
&repository,
156+
&config,
157+
&vec![commit_with_merge]
158+
),
159+
None
160+
);
161+
}
162+
}

0 commit comments

Comments
 (0)