about summary refs log tree commit diff
path: root/src/ci
diff options
context:
space:
mode:
Diffstat (limited to 'src/ci')
-rw-r--r--src/ci/github-actions/ci.yml4
-rwxr-xr-xsrc/ci/scripts/verify-backported-commits.sh130
2 files changed, 134 insertions, 0 deletions
diff --git a/src/ci/github-actions/ci.yml b/src/ci/github-actions/ci.yml
index 59a1d06dabd..6417f5a984a 100644
--- a/src/ci/github-actions/ci.yml
+++ b/src/ci/github-actions/ci.yml
@@ -206,6 +206,10 @@ x--expand-yaml-anchors--remove:
         run: src/ci/scripts/verify-line-endings.sh
         <<: *step
 
+      - name: ensure backported commits are in upstream branches
+        run: src/ci/scripts/verify-backported-commits.sh
+        <<: *step
+
       - name: run the build
         run: src/ci/scripts/run-build-from-ci.sh
         env:
diff --git a/src/ci/scripts/verify-backported-commits.sh b/src/ci/scripts/verify-backported-commits.sh
new file mode 100755
index 00000000000..75efabe8bfc
--- /dev/null
+++ b/src/ci/scripts/verify-backported-commits.sh
@@ -0,0 +1,130 @@
+#!/bin/bash
+# Ensure commits in beta are in master & commits in stable are in beta + master.
+set -euo pipefail
+IFS=$'\n\t'
+
+source "$(cd "$(dirname "$0")" && pwd)/../shared.sh"
+
+# We don't care about commits that predate this automation check, so we pass a
+# `<limit>` argument to `git cherry`.
+BETA_LIMIT="53fd98ca776cb875bc9e5514f56b52eb74f9e7a9"
+STABLE_LIMIT="a178d0322ce20e33eac124758e837cbd80a6f633"
+
+verify_backported_commits_main() {
+  ci_base_branch=$(ciBaseBranch)
+
+  if [[ "$ci_base_branch" != "beta" && "$ci_base_branch" != "stable" ]]; then
+    echo 'Skipping. This is only run when merging to the beta or stable branches.'
+    exit 0
+  fi
+
+  echo 'git: unshallowing the repository so we can check commits'
+  git fetch \
+    --no-tags \
+    --no-recurse-submodules \
+    --progress \
+    --prune \
+    --unshallow
+
+  if [[ $ci_base_branch == "beta" ]]; then
+    verify_cherries master "$BETA_LIMIT" \
+      || exit 1
+
+  elif [[ $ci_base_branch == "stable" ]]; then
+    (verify_cherries master "$STABLE_LIMIT" \
+      & verify_cherries beta "$STABLE_LIMIT") \
+      || exit 1
+
+  fi
+}
+
+# Verify all commits in `HEAD` are backports of a commit in <upstream>. See
+# https://git-scm.com/docs/git-cherry for an explanation of the arguments.
+#
+# $1 = <upstream>
+# $2 = <limit>
+verify_cherries() {
+  # commits that lack a `backport-of` comment.
+  local no_backports=()
+  # commits with an incorrect `backport-of` comment.
+  local bad_backports=()
+
+  commits=$(git cherry "origin/$1" HEAD "$2")
+
+  if [[ -z "$commits" ]]; then
+    echo "All commits in \`HEAD\` are present in \`$1\`"
+    return 0
+  fi
+
+  commits=$(echo "$commits" | grep '^\+' | cut -c 3-)
+
+  while read sha; do
+    # Check each commit in <current>..<upstream>
+    backport_sha=$(get_backport "$sha")
+
+    if [[ -z "$backport_sha" ]]; then
+      no_backports+=("$sha")
+      continue
+    fi
+
+    if ! is_in_master "$backport_sha"; then
+      bad_backports+=("$sha")
+      continue
+    fi
+
+    echo "✓ \`$sha\` backports \`$backport_sha\`"
+  done <<< "$commits"
+
+  failure=0
+
+  if [ ${#no_backports[@]} -ne 0 ]; then
+        echo 'Error: Could not find backports for all commits.'
+        echo
+        echo 'All commits in \`HEAD\` are required to have a corresponding upstream commit.'
+        echo 'It looks like the following commits:'
+        echo
+        for commit in "${no_backports[@]}"; do
+          echo "    $commit"
+        done
+        echo
+        echo "do not match any commits in \`$1\`. If this was intended, add the text"
+        echo '\`backport-of: <SHA of a commit already in master>\` somewhere in the'
+        echo 'message of each of these commits.'
+        echo
+        failure=1
+  fi
+
+  if [ ${#bad_backports[@]} -ne 0 ]; then
+        echo 'Error: Found incorrectly marked commits.'
+        echo
+        echo 'The following commits:'
+        echo
+        for commit in "${bad_backports[@]}"; do
+          echo "    $commit"
+        done
+        echo
+        echo 'have commit messages marked \`backport-of: <SHA>\`, but the SHA is not in'
+        echo '\`master\`.'
+        echo
+        failure=1
+  fi
+
+  return $failure
+}
+
+# Get the backport of a commit, or empty string if it does not exist.
+#
+# $1 = <sha>
+get_backport() {
+  git show -s --format=%B "$1" \
+    | sed -n 's/^.*backport-of:\s\?\([a-f0-9]\+\).*/\1/p'
+}
+
+# Check if a commit is in master.
+#
+# $1 = <sha>
+is_in_master() {
+  git merge-base --is-ancestor "$1" origin/master 2> /dev/null
+}
+
+verify_backported_commits_main