Auto merge of #105058 - Nilstrieb:no-merge-commits-for-you-only-bors-is-allowed-to-do-that, r=jyn514
Add tidy check to deny merge commits This will prevent users with the pre-push hook from pushing a merge commit. Exceptions are added for subtree updates. These exceptions are a little hacky and may be non-exhaustive but can be extended in the future. I added a link to `@jyn514's` blog post for the error case because that's the best resource to solve merge commits. But it would probably be better if it was integrated into https://rustc-dev-guide.rust-lang.org/git.html#no-merge-policy, then we could link that instead. r? `@jyn514`
This commit is contained in:
commit
4839886f0a
18 changed files with 195 additions and 60 deletions
15
.github/workflows/ci.yml
vendored
15
.github/workflows/ci.yml
vendored
|
@ -71,6 +71,11 @@ jobs:
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
|
- name: "checkout the `master` branch for tidy"
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: master
|
||||||
|
fetch-depth: 1
|
||||||
- name: configure the PR in which the error message will be posted
|
- name: configure the PR in which the error message will be posted
|
||||||
run: "echo \"[CI_PR_NUMBER=$num]\""
|
run: "echo \"[CI_PR_NUMBER=$num]\""
|
||||||
env:
|
env:
|
||||||
|
@ -485,6 +490,11 @@ jobs:
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
|
- name: "checkout the `master` branch for tidy"
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: master
|
||||||
|
fetch-depth: 1
|
||||||
- name: configure the PR in which the error message will be posted
|
- name: configure the PR in which the error message will be posted
|
||||||
run: "echo \"[CI_PR_NUMBER=$num]\""
|
run: "echo \"[CI_PR_NUMBER=$num]\""
|
||||||
env:
|
env:
|
||||||
|
@ -600,6 +610,11 @@ jobs:
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
|
- name: "checkout the `master` branch for tidy"
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: master
|
||||||
|
fetch-depth: 1
|
||||||
- name: configure the PR in which the error message will be posted
|
- name: configure the PR in which the error message will be posted
|
||||||
run: "echo \"[CI_PR_NUMBER=$num]\""
|
run: "echo \"[CI_PR_NUMBER=$num]\""
|
||||||
env:
|
env:
|
||||||
|
|
|
@ -259,6 +259,10 @@ dependencies = [
|
||||||
"toml",
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "build_helper"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bump-stage0"
|
name = "bump-stage0"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -5304,6 +5308,7 @@ dependencies = [
|
||||||
name = "tidy"
|
name = "tidy"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"build_helper",
|
||||||
"cargo_metadata 0.14.0",
|
"cargo_metadata 0.14.0",
|
||||||
"ignore",
|
"ignore",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
|
|
@ -4,6 +4,7 @@ members = [
|
||||||
"library/std",
|
"library/std",
|
||||||
"library/test",
|
"library/test",
|
||||||
"src/rustdoc-json-types",
|
"src/rustdoc-json-types",
|
||||||
|
"src/tools/build_helper",
|
||||||
"src/tools/cargotest",
|
"src/tools/cargotest",
|
||||||
"src/tools/clippy",
|
"src/tools/clippy",
|
||||||
"src/tools/clippy/clippy_dev",
|
"src/tools/clippy/clippy_dev",
|
||||||
|
|
|
@ -36,6 +36,7 @@ dependencies = [
|
||||||
name = "bootstrap"
|
name = "bootstrap"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"build_helper",
|
||||||
"cc",
|
"cc",
|
||||||
"cmake",
|
"cmake",
|
||||||
"fd-lock",
|
"fd-lock",
|
||||||
|
@ -70,6 +71,10 @@ dependencies = [
|
||||||
"regex-automata",
|
"regex-automata",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "build_helper"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.73"
|
version = "1.0.73"
|
||||||
|
|
|
@ -30,6 +30,7 @@ path = "bin/sccache-plus-cl.rs"
|
||||||
test = false
|
test = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
build_helper = { path = "../tools/build_helper" }
|
||||||
cmake = "0.1.38"
|
cmake = "0.1.38"
|
||||||
fd-lock = "3.0.8"
|
fd-lock = "3.0.8"
|
||||||
filetime = "0.2"
|
filetime = "0.2"
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use crate::builder::Builder;
|
use crate::builder::Builder;
|
||||||
use crate::util::{output, program_out_of_date, t};
|
use crate::util::{output, program_out_of_date, t};
|
||||||
|
use build_helper::git::get_rust_lang_rust_remote;
|
||||||
use ignore::WalkBuilder;
|
use ignore::WalkBuilder;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
@ -100,35 +101,6 @@ fn get_modified_rs_files(build: &Builder<'_>) -> Option<Vec<String>> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finds the remote for rust-lang/rust.
|
|
||||||
/// For example for these remotes it will return `upstream`.
|
|
||||||
/// ```text
|
|
||||||
/// origin https://github.com/Nilstrieb/rust.git (fetch)
|
|
||||||
/// origin https://github.com/Nilstrieb/rust.git (push)
|
|
||||||
/// upstream https://github.com/rust-lang/rust (fetch)
|
|
||||||
/// upstream https://github.com/rust-lang/rust (push)
|
|
||||||
/// ```
|
|
||||||
fn get_rust_lang_rust_remote() -> Result<String, String> {
|
|
||||||
let mut git = Command::new("git");
|
|
||||||
git.args(["config", "--local", "--get-regex", "remote\\..*\\.url"]);
|
|
||||||
|
|
||||||
let output = git.output().map_err(|err| format!("{err:?}"))?;
|
|
||||||
if !output.status.success() {
|
|
||||||
return Err("failed to execute git config command".to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
let stdout = String::from_utf8(output.stdout).map_err(|err| format!("{err:?}"))?;
|
|
||||||
|
|
||||||
let rust_lang_remote = stdout
|
|
||||||
.lines()
|
|
||||||
.find(|remote| remote.contains("rust-lang"))
|
|
||||||
.ok_or_else(|| "rust-lang/rust remote not found".to_owned())?;
|
|
||||||
|
|
||||||
let remote_name =
|
|
||||||
rust_lang_remote.split('.').nth(1).ok_or_else(|| "remote name not found".to_owned())?;
|
|
||||||
Ok(remote_name.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
struct RustfmtConfig {
|
struct RustfmtConfig {
|
||||||
ignore: Vec<String>,
|
ignore: Vec<String>,
|
||||||
|
|
|
@ -113,6 +113,7 @@ use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
|
use build_helper::ci::CiEnv;
|
||||||
use channel::GitInfo;
|
use channel::GitInfo;
|
||||||
use config::{DryRun, Target};
|
use config::{DryRun, Target};
|
||||||
use filetime::FileTime;
|
use filetime::FileTime;
|
||||||
|
@ -121,7 +122,7 @@ use once_cell::sync::OnceCell;
|
||||||
use crate::builder::Kind;
|
use crate::builder::Kind;
|
||||||
use crate::config::{LlvmLibunwind, TargetSelection};
|
use crate::config::{LlvmLibunwind, TargetSelection};
|
||||||
use crate::util::{
|
use crate::util::{
|
||||||
exe, libdir, mtime, output, run, run_suppressed, symlink_dir, try_run_suppressed, CiEnv,
|
exe, libdir, mtime, output, run, run_suppressed, symlink_dir, try_run_suppressed,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod bolt;
|
mod bolt;
|
||||||
|
|
|
@ -24,6 +24,8 @@ use crate::util::get_clang_cl_resource_dir;
|
||||||
use crate::util::{self, exe, output, t, up_to_date};
|
use crate::util::{self, exe, output, t, up_to_date};
|
||||||
use crate::{CLang, GitRepo};
|
use crate::{CLang, GitRepo};
|
||||||
|
|
||||||
|
use build_helper::ci::CiEnv;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct LlvmResult {
|
pub struct LlvmResult {
|
||||||
/// Path to llvm-config binary.
|
/// Path to llvm-config binary.
|
||||||
|
@ -217,7 +219,7 @@ pub(crate) fn is_ci_llvm_available(config: &Config, asserts: bool) -> bool {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if crate::util::CiEnv::is_ci() {
|
if CiEnv::is_ci() {
|
||||||
// We assume we have access to git, so it's okay to unconditionally pass
|
// We assume we have access to git, so it's okay to unconditionally pass
|
||||||
// `true` here.
|
// `true` here.
|
||||||
let llvm_sha = detect_llvm_sha(config, true);
|
let llvm_sha = detect_llvm_sha(config, true);
|
||||||
|
|
|
@ -255,35 +255,6 @@ pub enum CiEnv {
|
||||||
GitHubActions,
|
GitHubActions,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CiEnv {
|
|
||||||
/// Obtains the current CI environment.
|
|
||||||
pub fn current() -> CiEnv {
|
|
||||||
if env::var("TF_BUILD").map_or(false, |e| e == "True") {
|
|
||||||
CiEnv::AzurePipelines
|
|
||||||
} else if env::var("GITHUB_ACTIONS").map_or(false, |e| e == "true") {
|
|
||||||
CiEnv::GitHubActions
|
|
||||||
} else {
|
|
||||||
CiEnv::None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_ci() -> bool {
|
|
||||||
Self::current() != CiEnv::None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If in a CI environment, forces the command to run with colors.
|
|
||||||
pub fn force_coloring_in_ci(self, cmd: &mut Command) {
|
|
||||||
if self != CiEnv::None {
|
|
||||||
// Due to use of stamp/docker, the output stream of rustbuild is not
|
|
||||||
// a TTY in CI, so coloring is by-default turned off.
|
|
||||||
// The explicit `TERM=xterm` environment is needed for
|
|
||||||
// `--color always` to actually work. This env var was lost when
|
|
||||||
// compiling through the Makefile. Very strange.
|
|
||||||
cmd.env("TERM", "xterm").args(&["--color", "always"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn forcing_clang_based_tests() -> bool {
|
pub fn forcing_clang_based_tests() -> bool {
|
||||||
if let Some(var) = env::var_os("RUSTBUILD_FORCE_CLANG_BASED_TESTS") {
|
if let Some(var) = env::var_os("RUSTBUILD_FORCE_CLANG_BASED_TESTS") {
|
||||||
match &var.to_string_lossy().to_lowercase()[..] {
|
match &var.to_string_lossy().to_lowercase()[..] {
|
||||||
|
|
|
@ -103,6 +103,12 @@ x--expand-yaml-anchors--remove:
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
|
|
||||||
|
- name: checkout the `master` branch for tidy
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: master
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
# Rust Log Analyzer can't currently detect the PR number of a GitHub
|
# Rust Log Analyzer can't currently detect the PR number of a GitHub
|
||||||
# Actions build on its own, so a hint in the log message is needed to
|
# Actions build on its own, so a hint in the log message is needed to
|
||||||
# point it in the right direction.
|
# point it in the right direction.
|
||||||
|
|
8
src/tools/build_helper/Cargo.toml
Normal file
8
src/tools/build_helper/Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "build_helper"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
40
src/tools/build_helper/src/ci.rs
Normal file
40
src/tools/build_helper/src/ci.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub enum CiEnv {
|
||||||
|
/// Not a CI environment.
|
||||||
|
None,
|
||||||
|
/// The Azure Pipelines environment, for Linux (including Docker), Windows, and macOS builds.
|
||||||
|
AzurePipelines,
|
||||||
|
/// The GitHub Actions environment, for Linux (including Docker), Windows and macOS builds.
|
||||||
|
GitHubActions,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CiEnv {
|
||||||
|
/// Obtains the current CI environment.
|
||||||
|
pub fn current() -> CiEnv {
|
||||||
|
if std::env::var("TF_BUILD").map_or(false, |e| e == "True") {
|
||||||
|
CiEnv::AzurePipelines
|
||||||
|
} else if std::env::var("GITHUB_ACTIONS").map_or(false, |e| e == "true") {
|
||||||
|
CiEnv::GitHubActions
|
||||||
|
} else {
|
||||||
|
CiEnv::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_ci() -> bool {
|
||||||
|
Self::current() != CiEnv::None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If in a CI environment, forces the command to run with colors.
|
||||||
|
pub fn force_coloring_in_ci(self, cmd: &mut Command) {
|
||||||
|
if self != CiEnv::None {
|
||||||
|
// Due to use of stamp/docker, the output stream of rustbuild is not
|
||||||
|
// a TTY in CI, so coloring is by-default turned off.
|
||||||
|
// The explicit `TERM=xterm` environment is needed for
|
||||||
|
// `--color always` to actually work. This env var was lost when
|
||||||
|
// compiling through the Makefile. Very strange.
|
||||||
|
cmd.env("TERM", "xterm").args(&["--color", "always"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
src/tools/build_helper/src/git.rs
Normal file
30
src/tools/build_helper/src/git.rs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
/// Finds the remote for rust-lang/rust.
|
||||||
|
/// For example for these remotes it will return `upstream`.
|
||||||
|
/// ```text
|
||||||
|
/// origin https://github.com/Nilstrieb/rust.git (fetch)
|
||||||
|
/// origin https://github.com/Nilstrieb/rust.git (push)
|
||||||
|
/// upstream https://github.com/rust-lang/rust (fetch)
|
||||||
|
/// upstream https://github.com/rust-lang/rust (push)
|
||||||
|
/// ```
|
||||||
|
pub fn get_rust_lang_rust_remote() -> Result<String, String> {
|
||||||
|
let mut git = Command::new("git");
|
||||||
|
git.args(["config", "--local", "--get-regex", "remote\\..*\\.url"]);
|
||||||
|
|
||||||
|
let output = git.output().map_err(|err| format!("{err:?}"))?;
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err("failed to execute git config command".to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
let stdout = String::from_utf8(output.stdout).map_err(|err| format!("{err:?}"))?;
|
||||||
|
|
||||||
|
let rust_lang_remote = stdout
|
||||||
|
.lines()
|
||||||
|
.find(|remote| remote.contains("rust-lang"))
|
||||||
|
.ok_or_else(|| "rust-lang/rust remote not found".to_owned())?;
|
||||||
|
|
||||||
|
let remote_name =
|
||||||
|
rust_lang_remote.split('.').nth(1).ok_or_else(|| "remote name not found".to_owned())?;
|
||||||
|
Ok(remote_name.into())
|
||||||
|
}
|
2
src/tools/build_helper/src/lib.rs
Normal file
2
src/tools/build_helper/src/lib.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod ci;
|
||||||
|
pub mod git;
|
|
@ -5,6 +5,7 @@ edition = "2021"
|
||||||
autobins = false
|
autobins = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
build_helper = { path = "../build_helper" }
|
||||||
cargo_metadata = "0.14"
|
cargo_metadata = "0.14"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
miropt-test-tools = { path = "../miropt-test-tools" }
|
miropt-test-tools = { path = "../miropt-test-tools" }
|
||||||
|
|
|
@ -48,6 +48,7 @@ pub mod errors;
|
||||||
pub mod extdeps;
|
pub mod extdeps;
|
||||||
pub mod features;
|
pub mod features;
|
||||||
pub mod mir_opt_tests;
|
pub mod mir_opt_tests;
|
||||||
|
pub mod no_merge;
|
||||||
pub mod pal;
|
pub mod pal;
|
||||||
pub mod primitive_docs;
|
pub mod primitive_docs;
|
||||||
pub mod style;
|
pub mod style;
|
||||||
|
|
|
@ -107,6 +107,8 @@ fn main() {
|
||||||
check!(alphabetical, &compiler_path);
|
check!(alphabetical, &compiler_path);
|
||||||
check!(alphabetical, &library_path);
|
check!(alphabetical, &library_path);
|
||||||
|
|
||||||
|
check!(no_merge, ());
|
||||||
|
|
||||||
let collected = {
|
let collected = {
|
||||||
drain_handles(&mut handles);
|
drain_handles(&mut handles);
|
||||||
|
|
||||||
|
|
72
src/tools/tidy/src/no_merge.rs
Normal file
72
src/tools/tidy/src/no_merge.rs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
//! This check makes sure that no accidental merge commits are introduced to the repository.
|
||||||
|
//! It forbids all merge commits that are not caused by rollups/bors or subtree syncs.
|
||||||
|
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
use build_helper::ci::CiEnv;
|
||||||
|
use build_helper::git::get_rust_lang_rust_remote;
|
||||||
|
|
||||||
|
macro_rules! try_unwrap_in_ci {
|
||||||
|
($expr:expr) => {
|
||||||
|
match $expr {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(err) if CiEnv::is_ci() => {
|
||||||
|
panic!("Encountered error while testing Git status: {:?}", err)
|
||||||
|
}
|
||||||
|
Err(_) => return,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check(_: (), bad: &mut bool) {
|
||||||
|
let remote = try_unwrap_in_ci!(get_rust_lang_rust_remote());
|
||||||
|
let merge_commits = try_unwrap_in_ci!(find_merge_commits(&remote));
|
||||||
|
|
||||||
|
let mut bad_merge_commits = merge_commits.lines().filter(|commit| {
|
||||||
|
!(
|
||||||
|
// Bors is the ruler of merge commits.
|
||||||
|
commit.starts_with("Auto merge of") || commit.starts_with("Rollup merge of")
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(merge) = bad_merge_commits.next() {
|
||||||
|
tidy_error!(
|
||||||
|
bad,
|
||||||
|
"found a merge commit in the history: `{merge}`.
|
||||||
|
To resolve the issue, see this: https://rustc-dev-guide.rust-lang.org/git.html#i-made-a-merge-commit-by-accident.
|
||||||
|
If you're doing a subtree sync, add your tool to the list in the code that emitted this error."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs `git log --merges --format=%s $REMOTE/master..HEAD` and returns all commits
|
||||||
|
fn find_merge_commits(remote: &str) -> Result<String, String> {
|
||||||
|
let mut git = Command::new("git");
|
||||||
|
git.args([
|
||||||
|
"log",
|
||||||
|
"--merges",
|
||||||
|
"--format=%s",
|
||||||
|
&format!("{remote}/master..HEAD"),
|
||||||
|
// Ignore subtree syncs. Add your new subtrees here.
|
||||||
|
":!src/tools/miri",
|
||||||
|
":!src/tools/rust-analyzer",
|
||||||
|
":!compiler/rustc_smir",
|
||||||
|
":!library/portable-simd",
|
||||||
|
":!compiler/rustc_codegen_gcc",
|
||||||
|
":!src/tools/rustfmt",
|
||||||
|
":!compiler/rustc_codegen_cranelift",
|
||||||
|
":!src/tools/clippy",
|
||||||
|
]);
|
||||||
|
|
||||||
|
let output = git.output().map_err(|err| format!("{err:?}"))?;
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(format!(
|
||||||
|
"failed to execute git log command: {}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let stdout = String::from_utf8(output.stdout).map_err(|err| format!("{err:?}"))?;
|
||||||
|
|
||||||
|
Ok(stdout)
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue