diff --git a/.github/workflows/check-changelog.yml b/.github/workflows/check-changelog.yml new file mode 100644 index 000000000000..ce82fb7afed7 --- /dev/null +++ b/.github/workflows/check-changelog.yml @@ -0,0 +1,21 @@ +name: Check Changelog + +on: + push: + paths: + - 'CHANGELOG.md' + pull_request: + paths: + - 'CHANGELOG.md' + +jobs: + check-changelog: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Run check_changelog_prs script + run: | + bash ci/check_changelog_prs.sh diff --git a/ci/check_changelog_prs.sh b/ci/check_changelog_prs.sh new file mode 100755 index 000000000000..d5dad0d51c2c --- /dev/null +++ b/ci/check_changelog_prs.sh @@ -0,0 +1,141 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +set -euo pipefail + +# Function to compare versions +version_gt() { + local v1=$1 + local v2=$2 + # Remove 'v' prefix if present + v1=${v1#v} + v2=${v2#v} + # Compare versions using sort -V + [ "$(printf '%s\n' "$v1" "$v2" | sort -V | head -n1)" != "$v1" ] +} + +# Function to get git reference (tag or HEAD) +get_git_ref() { + local version=$1 + if git rev-parse "$version" >/dev/null 2>&1; then + echo "$version" + else + echo "HEAD" + fi +} + +# Configure PR types to ignore +IGNORE_TYPES=( + "docs" + "chore" + "test" + "ci" +) + +# Configure PR numbers to ignore +IGNORE_PRS=( + # 3.9.0 + 10655 10857 10858 10887 10959 11029 11041 11053 11055 11061 10976 10984 11025 + # 3.10.0 + 11105 11128 11169 11171 11280 11333 11081 11202 11469 + # 3.11.0 + 11463 11570 + # 3.12.0 + 11769 11816 11881 11905 11924 11926 11973 11991 11992 11829 +) + +# Build ignore pattern to match the following formats: +# - Direct keyword prefix (e.g., "docs:") +ignore_pattern=$(IFS="|"; echo "(${IGNORE_TYPES[*]}):|(${IGNORE_TYPES[*]})\([^)]*\):") + +# Get all versions from CHANGELOG.md +mapfile -t versions < <(grep -E '^## [0-9]+\.[0-9]+\.[0-9]+' CHANGELOG.md | sed 's/^## //') + +# Initialize error flag +has_errors=0 + +# Process each pair of consecutive versions +for ((i=0; i<${#versions[@]}-1; i++)); do + new_tag=${versions[i]} + old_tag=${versions[i+1]} + + # Skip if new_tag is less than or equal to 3.8.0 + if ! version_gt "$new_tag" "3.8.0"; then + continue + fi + + # Get git references + new_ref=$(get_git_ref "$new_tag") + old_ref=$(get_git_ref "$old_tag") + + echo -e "\n=== Checking changes between $new_tag ($new_ref) and $old_tag ($old_ref) ===" + + # Extract PRs between two versions from CHANGELOG.md + echo "Extracting PRs from CHANGELOG.md..." + changelog_prs=$(awk -v start="$new_tag" -v end="$old_tag" ' + BEGIN { flag = 0 } + $0 ~ "^## " { + if ($0 ~ start) { flag = 1; next } + if (flag && $0 ~ end) { flag = 0 } + } + flag { print } + ' CHANGELOG.md | grep -oE '#[0-9]+' | sort -n) + + # Extract actual PRs from git log, filtering out configured types and specified PR numbers + echo "Extracting actual PRs from git log (excluding: ${IGNORE_TYPES[*]} and PRs: ${IGNORE_PRS[*]})..." + git_prs=$(git log "$old_ref..$new_ref" --oneline | grep -vE "$ignore_pattern" | grep -oE '#[0-9]+' | sort -n) + + # Filter out specified PR numbers + for pr in "${IGNORE_PRS[@]}"; do + git_prs=$(echo "$git_prs" | grep -v "#$pr") + done + + # Compare the two lists + echo "Comparing PRs..." + missing_prs=$(comm -23 <(echo "$git_prs") <(echo "$changelog_prs")) + + # Print comparison results + echo -e "\n=== PR Comparison Results for $new_tag ===" + + if [ -z "$missing_prs" ]; then + echo -e "\n✅ All PRs are included in CHANGELOG.md for version $new_tag" + else + echo -e "\n❌ [ERROR] Missing PRs in CHANGELOG.md for version $new_tag (sorted):" + printf ' %s\n' "$missing_prs" + + # Get detailed information for each missing PR + echo -e "\nDetailed information about missing PRs for version $new_tag:" + for pr in $missing_prs; do + pr_num=${pr#\#} # Remove # symbol + echo -e "\nPR $pr :" + # Get PR commit information + git log "$old_ref..$new_ref" --oneline | grep "$pr" | while read -r line; do + echo " - $line" + done + # Try to get PR title (if possible) + echo " - PR URL: https://github.com/apache/apisix/pull/$pr_num" + done + echo "Note: If you confirm that a PR should not appear in the changelog, please add its number to the IGNORE_PRS array in this script." + has_errors=1 + fi +done + +# Exit with error if any version had missing PRs +if [ $has_errors -eq 1 ]; then + exit 1 +fi