|
| 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