diff --git a/.github/scripts/auto-add-milestone.sh b/.github/scripts/auto-add-milestone.sh new file mode 100755 index 000000000000..7b1dacdc2a98 --- /dev/null +++ b/.github/scripts/auto-add-milestone.sh @@ -0,0 +1,86 @@ +#!/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 -e + +get_milestone_number() { + local milestone_title="$1" + local number=$(gh api "repos/$REPOSITORY/milestones" --jq ".[] | select(.title==\"$milestone_title\") | .number") + + if [ -z "$number" ]; then + echo "Creating milestone: $milestone_title" + number=$(gh api "repos/$REPOSITORY/milestones" -f title="$milestone_title" --jq '.number') + fi + + echo "$number" +} + +create_backport_issue() { + local pr_number="$1" + local pr_title="$2" + local target_milestone="$3" + + local milestone_number=$(get_milestone_number "$target_milestone") + local message="Backport #$pr_number to release branch for version $target_milestone" + local note="Add the \`approved\` label to start the automatic backport process." + local body=$(jq -n --arg source_pr "$pr_number" --arg target_version "$target_milestone" --arg message "$message" --arg note "$note" '$ARGS.named') + + gh api "repos/$REPOSITORY/issues" \ + -f title="[$target_milestone] $pr_title" \ + -f body="$body" \ + -f milestone="$milestone_number" \ + -f labels[]="backport" \ + --jq '.number' +} + +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + echo "Example: $0 12345 apache/druid" + exit 1 +fi + +PR_NUMBER="$1" +REPOSITORY="$2" + +VERSION=$(xmllint --xpath "/*[local-name()='project']/*[local-name()='version']/text()" pom.xml | sed 's/-SNAPSHOT//') + +if [ -z "$VERSION" ]; then + echo "Error: Could not extract version from pom.xml" + exit 1 +fi + +echo "Extracted version: $VERSION" + +EXISTING_MILESTONE=$(gh api "repos/$REPOSITORY/issues/$PR_NUMBER" --jq '.milestone.title // empty') + +if [ -n "$EXISTING_MILESTONE" ] && [ "$EXISTING_MILESTONE" != "$VERSION" ]; then + echo "PR #$PR_NUMBER has milestone $EXISTING_MILESTONE, but should be $VERSION" + + PR_TITLE=$(gh api "repos/$REPOSITORY/issues/$PR_NUMBER" --jq '.title') + BACKPORT_ISSUE=$(create_backport_issue "$PR_NUMBER" "$PR_TITLE" "$EXISTING_MILESTONE") + + echo "Created backport issue #$BACKPORT_ISSUE for milestone $EXISTING_MILESTONE" +elif [ -n "$EXISTING_MILESTONE" ]; then + echo "PR #$PR_NUMBER already has correct milestone: $EXISTING_MILESTONE" + exit 0 +fi + +MILESTONE_NUMBER=$(get_milestone_number "$VERSION") + +echo "Adding PR #$PR_NUMBER to milestone $VERSION" +gh api "repos/$REPOSITORY/issues/$PR_NUMBER" -f milestone="$MILESTONE_NUMBER" -X PATCH +echo "Successfully added PR #$PR_NUMBER to milestone $VERSION" diff --git a/.github/scripts/backport-pr.sh b/.github/scripts/backport-pr.sh new file mode 100755 index 000000000000..1d7599674bcd --- /dev/null +++ b/.github/scripts/backport-pr.sh @@ -0,0 +1,43 @@ +#!/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 -e + +if [ "$#" -lt 3 ] || [ "$#" -gt 4 ]; then + echo "Usage: $0 [body]" + echo "Example: $0 abc123def456 37.0.0 'Fix bug in query engine' '{\"backport_issue\": \"12345\"}'" + exit 1 +fi + +COMMIT_HASH="$1" +TARGET_BRANCH="$2" +PR_TITLE="$3" +BODY="${4:-Automatic backport to $TARGET_BRANCH}" + +BACKPORT_BRANCH="backport-$COMMIT_HASH-to-$TARGET_BRANCH" + +git fetch origin "$TARGET_BRANCH" +git checkout -b "$BACKPORT_BRANCH" "origin/$TARGET_BRANCH" +git cherry-pick -x "$COMMIT_HASH" +git push origin "$BACKPORT_BRANCH" + +gh pr create \ + --base "$TARGET_BRANCH" \ + --head "$BACKPORT_BRANCH" \ + --title "$PR_TITLE" \ + --body "$BODY" \ + --label "backport" diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml new file mode 100644 index 000000000000..757ce3768c4c --- /dev/null +++ b/.github/workflows/backport.yml @@ -0,0 +1,71 @@ +# +# 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. +# + +name: "Backport PR" + +on: + issues: + types: [labeled] + +env: + ISSUE_NUMBER: ${{ github.event.issue.number }} + ISSUE_TITLE: ${{ github.event.issue.title }} + +jobs: + backport: + if: github.repository == 'apache/druid' && github.event.label.name == 'approved' && contains(github.event.issue.labels.*.name, 'backport') + permissions: + contents: write + pull-requests: write + issues: write + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Parse issue and create backport PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SOURCE_PR: ${{ fromJson(github.event.issue.body).source_pr }} + TARGET_VERSION: ${{ fromJson(github.event.issue.body).target_version }} + run: | + MERGE_COMMIT=$(gh api "repos/${{ github.repository }}/pulls/$SOURCE_PR" --jq '.merge_commit_sha') + + if [ -z "$MERGE_COMMIT" ]; then + echo "Error: Could not find merge commit for PR #$SOURCE_PR" + exit 1 + fi + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + MESSAGE="Backport for #$ISSUE_NUMBER" + BODY=$(jq -n --arg backport_issue "$ISSUE_NUMBER" --arg message "$MESSAGE" '$ARGS.named') + + .github/scripts/backport-pr.sh "$MERGE_COMMIT" "$TARGET_VERSION" "[Backport] $ISSUE_TITLE" "$BODY" + + - name: Comment on failure + if: failure() + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh api "repos/${{ github.repository }}/issues/$ISSUE_NUMBER/comments" \ + -f body="❌ Automatic backport failed. See workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" diff --git a/.github/workflows/pr-merged.yml b/.github/workflows/pr-merged.yml new file mode 100644 index 000000000000..9e62d726706d --- /dev/null +++ b/.github/workflows/pr-merged.yml @@ -0,0 +1,47 @@ +# +# 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. +# + +name: "PR Merged" + +on: + pull_request: + types: [closed] + +jobs: + add-milestone: + if: github.repository == 'apache/druid' && github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'master' + permissions: + contents: read + pull-requests: write + issues: write + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install xmllint + run: | + sudo apt-get update + sudo apt-get install -y libxml2-utils + + - name: Add milestone to PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + .github/scripts/auto-add-milestone.sh "${{ github.event.pull_request.number }}" "${{ github.repository }}"