diff --git a/.github/scripts/releasing/check-merge-back-commits.sh b/.github/scripts/releasing/check-merge-back-commits.sh new file mode 100755 index 0000000000..3df9f6f3e7 --- /dev/null +++ b/.github/scripts/releasing/check-merge-back-commits.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +# Exit the script as soon as an error occurs. +set -euo pipefail + +# This script fails if merges a release back into , +# but also adds commits that aren't on a release or staging branch, see RELEASING.md. +# Merge commits are allowed. +# Usage: .github/scripts/releasing/check-merge-back-commits.sh + +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +BASE=$1 +HEAD=$2 + +SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}") +# shellcheck source=.github/scripts/releasing/common.sh +source "${SCRIPT_DIR}/common.sh" +load_release_branches + +# A PR is a merge-back if some of its commits are on a release or staging branch. +PR_COUNT=$(git rev-list --no-merges --count "${BASE}..${HEAD}") +NEW_COUNT=$(git rev-list --no-merges --count "${BASE}..${HEAD}" --not "${BRANCHES[@]}") +if ((NEW_COUNT == PR_COUNT)); then + echo "This PR doesn't merge a release back." + exit 0 +fi +if ((NEW_COUNT == 0)); then + echo "This merge-back adds no commits of its own." + exit 0 +fi + +echo "This PR merges a release back, but also adds commits that aren't on a release or staging branch:" +git log --no-merges --format=' %h %s' "${BASE}..${HEAD}" --not "${BRANCHES[@]}" +echo +echo "Make these changes in a separate PR, see RELEASING.md." +exit 1 diff --git a/.github/scripts/releasing/check-no-copied-release-commits.sh b/.github/scripts/releasing/check-no-copied-release-commits.sh index abffea876b..c85f9dec2d 100755 --- a/.github/scripts/releasing/check-no-copied-release-commits.sh +++ b/.github/scripts/releasing/check-no-copied-release-commits.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Exit the script as soon as an error occurs. set -euo pipefail @@ -21,11 +21,10 @@ patch_ids() { git log --no-merges --patch --no-color --no-ext-diff "$@" | git patch-id --stable | sort } -mapfile -t BRANCHES < <(git for-each-ref --format='%(refname)' 'refs/remotes/*/release/*' 'refs/remotes/*/staging/*') -if [ "${#BRANCHES[@]}" -eq 0 ]; then - echo "Error: No release or staging branches found." - exit 1 -fi +SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}") +# shellcheck source=.github/scripts/releasing/common.sh +source "${SCRIPT_DIR}/common.sh" +load_release_branches # Each line is " ". RELEASE_PATCHES=$(patch_ids "${BRANCHES[@]}" --not "${HEAD}") diff --git a/.github/scripts/releasing/check-no-merge-back.sh b/.github/scripts/releasing/check-no-merge-back.sh new file mode 100755 index 0000000000..51cff7de16 --- /dev/null +++ b/.github/scripts/releasing/check-no-merge-back.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +# Exit the script as soon as an error occurs. +set -euo pipefail + +# This script fails if contains release or staging commits that does not, +# i.e. if merges a release back into . +# Used in the merge queue, which squashes PRs and would drop the merge commit, see RELEASING.md. +# Usage: .github/scripts/releasing/check-no-merge-back.sh + +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +BASE=$1 +HEAD=$2 + +SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}") +# shellcheck source=.github/scripts/releasing/common.sh +source "${SCRIPT_DIR}/common.sh" +load_release_branches + +RELEASE_COMMITS=$(git rev-list "${BRANCHES[@]}" --not "${BASE}") +HEAD_COMMITS=$(git rev-list "${BASE}..${HEAD}") +# The release commits in , newest first. +MERGED=$(grep -xF -f <(echo "${RELEASE_COMMITS}") <<<"${HEAD_COMMITS}" || true) +if [ -z "${MERGED}" ]; then + echo "No release commits are merged back." + exit 0 +fi + +echo "This PR merges $(wc -l <<<"${MERGED}" | tr -d ' ') release commits back, e.g.:" +head -5 <<<"${MERGED}" | xargs git log --no-walk --format=' %h %s' +echo +echo "Merge-backs must not go through the merge queue, which squashes them." +echo "Fast-forward develop to the PR branch instead, see RELEASING.md." +exit 1 diff --git a/.github/scripts/releasing/check-releases-merged.sh b/.github/scripts/releasing/check-releases-merged.sh index 75751a8e3b..950634ea8f 100755 --- a/.github/scripts/releasing/check-releases-merged.sh +++ b/.github/scripts/releasing/check-releases-merged.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Exit the script as soon as an error occurs. set -euo pipefail diff --git a/.github/scripts/releasing/common.sh b/.github/scripts/releasing/common.sh new file mode 100644 index 0000000000..308bb93d72 --- /dev/null +++ b/.github/scripts/releasing/common.sh @@ -0,0 +1,12 @@ +# shellcheck shell=bash + +# Helpers shared by the release checks in this directory, see RELEASING.md. + +# Sets BRANCHES to all release and staging branches, and fails if there are none. +load_release_branches() { + mapfile -t BRANCHES < <(git for-each-ref --format='%(refname)' 'refs/remotes/*/release/*' 'refs/remotes/*/staging/*') + if [ "${#BRANCHES[@]}" -eq 0 ]; then + echo "Error: No release or staging branches found." + exit 1 + fi +} diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index 7457ca32cd..69051d5977 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -166,10 +166,31 @@ jobs: fetch-depth: 0 persist-credentials: false - name: Check for copied release commits + if: ${{ github.event_name == 'pull_request' }} env: - BASE: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha }} - HEAD: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} + BASE: ${{ github.event.pull_request.base.sha }} + HEAD: ${{ github.event.pull_request.head.sha }} run: .github/scripts/releasing/check-no-copied-release-commits.sh "${BASE}" "${HEAD}" + # Runs even if the previous check fails, so that both problems are reported at once. + - name: Check merge-back has no new commits + if: ${{ !cancelled() && github.event_name == 'pull_request' }} + env: + BASE: ${{ github.event.pull_request.base.sha }} + HEAD: ${{ github.event.pull_request.head.sha }} + run: .github/scripts/releasing/check-merge-back-commits.sh "${BASE}" "${HEAD}" + # The queue squashes PRs, so check the PR's own branch, named in the queue branch. + - name: Check the merge queue doesn't merge a release back + if: ${{ github.event_name == 'merge_group' }} + env: + BASE: ${{ github.event.merge_group.base_sha }} + HEAD_REF: ${{ github.event.merge_group.head_ref }} + run: | + if ! [[ "${HEAD_REF}" =~ /pr-([0-9]+)-[0-9a-f]+$ ]]; then + echo "Error: Can't find the PR number in '${HEAD_REF}'." + exit 1 + fi + git fetch --no-tags origin "refs/pull/${BASH_REMATCH[1]}/head" + .github/scripts/releasing/check-no-merge-back.sh "${BASE}" FETCH_HEAD clang-tidy: needs: should-run