[rlc-9/5.14.0-687.17.1.el9_8] Multiple patches tested (11 commits) #377
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: LT Rebase Merge | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| pr_number: | |
| description: 'PR number to merge' | |
| required: true | |
| type: number | |
| issue_comment: | |
| types: [created] | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| jobs: | |
| lt-rebase-merge: | |
| # Only run on PRs when comment starts with /lt_rebase_merge, or when manually dispatched | |
| if: | | |
| github.event_name == 'workflow_dispatch' || | |
| (github.event.issue.pull_request && startsWith(github.event.comment.body, '/lt_rebase_merge')) | |
| runs-on: ubuntu-latest | |
| container: | |
| image: rockylinux:9 | |
| steps: | |
| - name: Generate GitHub App token | |
| id: generate-token | |
| uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 | |
| with: | |
| client-id: ${{ secrets.APP_ID }} | |
| private-key: ${{ secrets.APP_PRIVATE_KEY }} | |
| repositories: | | |
| kernel-src-tree | |
| kernel-src-tree-tools | |
| - name: Install system dependencies | |
| run: | | |
| dnf install -y git jq | |
| # Install GitHub CLI | |
| dnf install 'dnf-command(config-manager)' -y | |
| dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo | |
| dnf install gh -y | |
| - name: Validate commenter has write access | |
| if: github.event_name == 'issue_comment' | |
| env: | |
| GH_TOKEN: ${{ steps.generate-token.outputs.token }} | |
| COMMENTER: ${{ github.event.comment.user.login }} | |
| REPOSITORY: ${{ github.repository }} | |
| run: | | |
| set -e | |
| # Check if commenter has write or admin permission on the repository | |
| PERMISSION=$(gh api "/repos/$REPOSITORY/collaborators/$COMMENTER/permission" --jq '.permission' 2>/dev/null || echo "none") | |
| echo "User $COMMENTER has permission level: $PERMISSION" | |
| if [[ "$PERMISSION" == "admin" || "$PERMISSION" == "write" ]]; then | |
| echo "✅ User $COMMENTER has sufficient permissions" | |
| else | |
| echo "❌ User $COMMENTER does not have write or admin access to $REPOSITORY" | |
| echo "Only users with write or admin access can trigger lt_rebase_merge" | |
| exit 1 | |
| fi | |
| - name: Get PR details and verify approvals | |
| env: | |
| GH_TOKEN: ${{ steps.generate-token.outputs.token }} | |
| PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.issue.number }} | |
| REPOSITORY: ${{ github.repository }} | |
| run: | | |
| set -e | |
| # Validate PR number is actually a number | |
| if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then | |
| echo "❌ Security: Invalid PR number format: $PR_NUMBER" | |
| exit 1 | |
| fi | |
| # Validate PR number is reasonable (1 to 7 digits) | |
| if [ "$PR_NUMBER" -lt 1 ] || [ "$PR_NUMBER" -gt 9999999 ]; then | |
| echo "❌ Security: PR number out of range: $PR_NUMBER" | |
| exit 1 | |
| fi | |
| # Validate repository format (owner/repo) | |
| if ! [[ "$REPOSITORY" =~ ^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$ ]]; then | |
| echo "❌ Security: Invalid repository format: $REPOSITORY" | |
| exit 1 | |
| fi | |
| # Validate repository name length | |
| if [ ${#REPOSITORY} -gt 100 ]; then | |
| echo "❌ Security: Repository name too long" | |
| exit 1 | |
| fi | |
| echo "✅ All input validation passed" | |
| # Get PR details | |
| PR_DATA=$(gh pr view "$PR_NUMBER" --repo "$REPOSITORY" --json number,headRefName,baseRefName,title,reviews) | |
| # Extract branch names and PR title | |
| HEAD_BRANCH=$(echo "$PR_DATA" | jq -r '.headRefName') | |
| BASE_BRANCH=$(echo "$PR_DATA" | jq -r '.baseRefName') | |
| PR_TITLE=$(echo "$PR_DATA" | jq -r '.title') | |
| # Validate PR title to prevent injection via GITHUB_ENV | |
| if [ ${#PR_TITLE} -gt 500 ]; then | |
| echo "❌ Security: PR title too long (${#PR_TITLE} > 500)" | |
| exit 1 | |
| fi | |
| # Validate branch names to prevent injection via GITHUB_ENV | |
| BRANCH_REGEX='^[a-zA-Z0-9/_.{}-]+$' | |
| MAX_BRANCH_LENGTH=255 | |
| for BR_VAR in HEAD_BRANCH BASE_BRANCH; do | |
| BR_VALUE=${!BR_VAR} | |
| if [ -z "$BR_VALUE" ]; then | |
| echo "❌ $BR_VAR is empty, aborting." | |
| exit 1 | |
| fi | |
| if ! [[ "$BR_VALUE" =~ $BRANCH_REGEX ]]; then | |
| echo "❌ Security: Invalid branch name in $BR_VAR: $BR_VALUE" | |
| exit 1 | |
| fi | |
| if [ ${#BR_VALUE} -gt $MAX_BRANCH_LENGTH ]; then | |
| echo "❌ Security: Branch name in $BR_VAR too long (${#BR_VALUE} > $MAX_BRANCH_LENGTH)" | |
| exit 1 | |
| fi | |
| done | |
| echo "✅ Branch name validation passed" | |
| echo "HEAD_BRANCH=$HEAD_BRANCH" >> $GITHUB_ENV | |
| echo "BASE_BRANCH=$BASE_BRANCH" >> $GITHUB_ENV | |
| echo "PR_TITLE=$PR_TITLE" >> $GITHUB_ENV | |
| # Count approvals based on the latest review state per reviewer | |
| APPROVALS=$(echo "$PR_DATA" | jq '[.reviews | |
| | sort_by(.author.login, .submittedAt) | |
| | group_by(.author.login)[] | |
| | last | |
| | select(.state == "APPROVED") | |
| ] | length') | |
| echo "PR #$PR_NUMBER has $APPROVALS approvals" | |
| if [ "$APPROVALS" -lt 2 ]; then | |
| echo "❌ PR requires at least 2 approvals to merge. Current approvals: $APPROVALS" | |
| exit 1 | |
| fi | |
| echo "✅ PR has sufficient approvals ($APPROVALS)" | |
| - name: Extract LT version from PR | |
| run: | | |
| set -e | |
| # Extract version from head branch name | |
| # Expected patterns: {automation_tmp}_ciq-6.XX.y-next or similar | |
| if [[ "$HEAD_BRANCH" =~ ciq-([0-9]+)\.([0-9]+)\.y ]]; then | |
| LT_MAJOR="${BASH_REMATCH[1]}" | |
| LT_MINOR="${BASH_REMATCH[2]}" | |
| echo "Extracted version from branch: $LT_MAJOR.$LT_MINOR" | |
| else | |
| echo "❌ Could not extract LT version from branch name '$HEAD_BRANCH'" | |
| echo "Branch name must match pattern: ciq-X.Y.y" | |
| exit 1 | |
| fi | |
| echo "LT_MAJOR=$LT_MAJOR" >> $GITHUB_ENV | |
| echo "LT_MINOR=$LT_MINOR" >> $GITHUB_ENV | |
| echo "LT_VERSION=$LT_MAJOR.$LT_MINOR" >> $GITHUB_ENV | |
| # Construct branch names | |
| echo "AUTOMATION_TMP_BRANCH={automation_tmp}_ciq-${LT_MAJOR}.${LT_MINOR}.y-next" >> $GITHUB_ENV | |
| echo "NEXT_BRANCH=ciq-${LT_MAJOR}.${LT_MINOR}.y-next" >> $GITHUB_ENV | |
| echo "TARGET_BRANCH=ciq-${LT_MAJOR}.${LT_MINOR}.y" >> $GITHUB_ENV | |
| echo "✅ LT Version: $LT_MAJOR.$LT_MINOR" | |
| - name: Validate PR target branch | |
| run: | | |
| set -e | |
| # Verify BASE_BRANCH matches expected pattern (ciq-X.Y.y-next) | |
| if ! echo "$BASE_BRANCH" | grep -qE '^ciq-[0-9]+\.[0-9]+\.y-next$'; then | |
| echo "❌ PR base branch '$BASE_BRANCH' does not match expected pattern 'ciq-X.Y.y-next'" | |
| exit 1 | |
| fi | |
| # Verify BASE_BRANCH matches the version extracted from HEAD_BRANCH | |
| EXPECTED_BASE="ciq-${LT_MAJOR}.${LT_MINOR}.y-next" | |
| if [ "$BASE_BRANCH" != "$EXPECTED_BASE" ]; then | |
| echo "❌ PR base branch '$BASE_BRANCH' does not match expected '$EXPECTED_BASE'" | |
| echo "HEAD_BRANCH version: $LT_MAJOR.$LT_MINOR" | |
| exit 1 | |
| fi | |
| echo "✅ PR base branch is correct: $BASE_BRANCH" | |
| - name: Checkout kernel-src-tree | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| repository: ctrliq/kernel-src-tree | |
| token: ${{ steps.generate-token.outputs.token }} | |
| path: kernel-src-tree | |
| fetch-depth: 0 | |
| persist-credentials: true | |
| - name: Checkout kernel-src-tree-tools | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| repository: ctrliq/kernel-src-tree-tools | |
| token: ${{ steps.generate-token.outputs.token }} | |
| path: kernel-src-tree-tools | |
| persist-credentials: true | |
| - name: Configure git | |
| run: | | |
| git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git config --global user.name "github-actions[bot]" | |
| - name: Park open PRs on temporary branch | |
| env: | |
| GH_TOKEN: ${{ steps.generate-token.outputs.token }} | |
| REPOSITORY: ${{ github.repository }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: | | |
| set -e | |
| OPEN_PRS=$(gh pr list --repo "$REPOSITORY" --base "$TARGET_BRANCH" --state open --limit 200 --json number --jq '[.[].number | tostring] | join(" ")') | |
| if [ -z "$OPEN_PRS" ]; then | |
| echo "No open PRs targeting ${TARGET_BRANCH}" | |
| exit 0 | |
| fi | |
| TEMP_BRANCH="_pr_parking_${TARGET_BRANCH}_run${GITHUB_RUN_ID}" | |
| echo "Parking PRs on ${TEMP_BRANCH}: ${OPEN_PRS}" | |
| cd kernel-src-tree | |
| git fetch origin "${TARGET_BRANCH}" --quiet | |
| git push origin "origin/${TARGET_BRANCH}:refs/heads/${TEMP_BRANCH}" | |
| echo "TEMP_BRANCH=$TEMP_BRANCH" >> $GITHUB_ENV | |
| for pr in $OPEN_PRS; do | |
| echo "Parking PR #$pr on ${TEMP_BRANCH}" | |
| gh pr edit "$pr" --repo "$REPOSITORY" --base "${TEMP_BRANCH}" | |
| printf -v COMMENT_BODY ':hourglass_flowing_sand: A rebase of `%s` to the latest upstream stable release is in progress. This PR has been temporarily moved to `%s` while the branch is updated.\n\nWorkflow run: %s' "$TARGET_BRANCH" "$TEMP_BRANCH" "$RUN_URL" | |
| gh pr comment "$pr" --repo "$REPOSITORY" --body "$COMMENT_BODY" || true | |
| done | |
| - name: Setup branch references and execute lt_rebase_merge.sh | |
| id: rebase-merge | |
| run: | | |
| set -e | |
| cd kernel-src-tree | |
| # Get local references to the required branches | |
| echo "Fetching required branches..." | |
| for branch in "$AUTOMATION_TMP_BRANCH" "$NEXT_BRANCH" "$TARGET_BRANCH"; do | |
| if ! git fetch origin "$branch:$branch"; then | |
| echo "❌ Failed to fetch branch: $branch" | |
| exit 1 | |
| fi | |
| done | |
| echo "✅ Branch references set up" | |
| echo "Branches:" | |
| echo " - Automation Tmp: $AUTOMATION_TMP_BRANCH" | |
| echo " - Next: $NEXT_BRANCH" | |
| echo " - Target: $TARGET_BRANCH" | |
| # Execute the lt_rebase_merge.sh script | |
| echo "Executing lt_rebase_merge.sh..." | |
| if ! OUTPUT=$(../kernel-src-tree-tools/lt_rebase_merge.sh "$AUTOMATION_TMP_BRANCH" "$TARGET_BRANCH" 2>&1); then | |
| echo "❌ lt_rebase_merge.sh failed with output:" | |
| echo "$OUTPUT" | |
| exit 1 | |
| fi | |
| echo "✅ lt_rebase_merge.sh completed successfully" | |
| - name: Restore parked PRs to new branch | |
| if: always() | |
| env: | |
| GH_TOKEN: ${{ steps.generate-token.outputs.token }} | |
| REPOSITORY: ${{ github.repository }} | |
| SCRIPT_OUTCOME: ${{ steps.rebase-merge.outcome }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: | | |
| if [ -z "$TEMP_BRANCH" ]; then | |
| echo "No temp branch, nothing to restore" | |
| exit 0 | |
| fi | |
| if ! PARKED_PRS=$(gh pr list --repo "$REPOSITORY" --base "$TEMP_BRANCH" --state open --limit 200 --json number --jq '[.[].number | tostring] | join(" ")'); then | |
| echo "❌ Failed to list PRs on ${TEMP_BRANCH}, cannot restore" | |
| exit 1 | |
| fi | |
| if [ -z "$PARKED_PRS" ]; then | |
| echo "No PRs on ${TEMP_BRANCH}, cleaning up" | |
| cd kernel-src-tree | |
| git push origin --delete "${TEMP_BRANCH}" 2>/dev/null || true | |
| exit 0 | |
| fi | |
| if [ "$SCRIPT_OUTCOME" = "success" ]; then | |
| cd kernel-src-tree | |
| git fetch origin --tags --quiet 2>/dev/null || true | |
| NEW_VERSION=$(git describe --tags --abbrev=0 --match='v*' "origin/${TARGET_BRANCH}" 2>/dev/null || echo "the latest upstream release") | |
| RESTORE_FAILURES=0 | |
| for pr in $PARKED_PRS; do | |
| echo "Retargeting PR #$pr to ${TARGET_BRANCH}" | |
| if ! gh pr edit "$pr" --repo "$REPOSITORY" --base "${TARGET_BRANCH}"; then | |
| echo "❌ Failed to retarget PR #$pr" | |
| printf -v COMMENT_BODY ':warning: Failed to retarget this PR back to `%s` after rebase. This PR may still be on temporary branch `%s`. Please manually update the base branch.\n\nWorkflow run: %s' "$TARGET_BRANCH" "$TEMP_BRANCH" "$RUN_URL" | |
| gh pr comment "$pr" --repo "$REPOSITORY" --body "$COMMENT_BODY" || true | |
| RESTORE_FAILURES=$((RESTORE_FAILURES + 1)) | |
| continue | |
| fi | |
| printf -v COMMENT_BODY ':white_check_mark: `%s` has been rebased to `%s`. This PR has been retargeted to the updated branch. You will need to rebase your PR branch onto the new `%s`.\n\nWorkflow run: %s' "$TARGET_BRANCH" "$NEW_VERSION" "$TARGET_BRANCH" "$RUN_URL" | |
| gh pr comment "$pr" --repo "$REPOSITORY" --body "$COMMENT_BODY" || true | |
| done | |
| if [ "$RESTORE_FAILURES" -gt 0 ]; then | |
| echo "⚠️ Failed to retarget $RESTORE_FAILURES PR(s), leaving ${TEMP_BRANCH} intact" | |
| else | |
| echo "Cleaning up temporary branch ${TEMP_BRANCH}" | |
| git push origin --delete "${TEMP_BRANCH}" 2>/dev/null || true | |
| fi | |
| elif [ "$SCRIPT_OUTCOME" = "skipped" ]; then | |
| # Script never ran — TARGET_BRANCH is intact, safe to move PRs back | |
| echo "Rebase was skipped, restoring PRs to ${TARGET_BRANCH}" | |
| RESTORE_FAILURES=0 | |
| for pr in $PARKED_PRS; do | |
| echo "Restoring PR #$pr to ${TARGET_BRANCH}" | |
| if ! gh pr edit "$pr" --repo "$REPOSITORY" --base "${TARGET_BRANCH}"; then | |
| echo "❌ Failed to restore PR #$pr" | |
| printf -v COMMENT_BODY ':warning: Failed to move this PR back to `%s`. This PR may still be on temporary branch `%s`. Please manually update the base branch.\n\nWorkflow run: %s' "$TARGET_BRANCH" "$TEMP_BRANCH" "$RUN_URL" | |
| gh pr comment "$pr" --repo "$REPOSITORY" --body "$COMMENT_BODY" || true | |
| RESTORE_FAILURES=$((RESTORE_FAILURES + 1)) | |
| continue | |
| fi | |
| printf -v COMMENT_BODY ':warning: The rebase of `%s` could not proceed. This PR has been moved back to `%s`. No changes were made to the branch.\n\nWorkflow run: %s' "$TARGET_BRANCH" "$TARGET_BRANCH" "$RUN_URL" | |
| gh pr comment "$pr" --repo "$REPOSITORY" --body "$COMMENT_BODY" || true | |
| done | |
| if [ "$RESTORE_FAILURES" -gt 0 ]; then | |
| echo "⚠️ Failed to restore $RESTORE_FAILURES PR(s), leaving ${TEMP_BRANCH} intact" | |
| else | |
| echo "Cleaning up temporary branch ${TEMP_BRANCH}" | |
| cd kernel-src-tree | |
| git push origin --delete "${TEMP_BRANCH}" 2>/dev/null || true | |
| fi | |
| else | |
| # Script ran and failed — TARGET_BRANCH may be in unknown state, don't move PRs | |
| echo "⚠️ Rebase merge failed, leaving PRs on ${TEMP_BRANCH}" | |
| for pr in $PARKED_PRS; do | |
| printf -v COMMENT_BODY ':warning: The rebase of `%s` failed. This PR remains on temporary branch `%s` to prevent it from being closed. The kernel team is investigating the failure.\n\nWorkflow run: %s' "$TARGET_BRANCH" "$TEMP_BRANCH" "$RUN_URL" | |
| gh pr comment "$pr" --repo "$REPOSITORY" --body "$COMMENT_BODY" || true | |
| done | |
| fi | |
| - name: Comment on PR with result | |
| if: always() | |
| env: | |
| GH_TOKEN: ${{ steps.generate-token.outputs.token }} | |
| PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.issue.number }} | |
| REPOSITORY: ${{ github.repository }} | |
| JOB_STATUS: ${{ job.status }} | |
| run: | | |
| RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| if [ -z "${LT_VERSION:-}" ]; then | |
| EMOJI="❌" | |
| TITLE="LT Rebase Merge failed" | |
| MESSAGE="Could not determine LT version, rebase merge aborted. Please check the workflow run for details." | |
| elif [ "$JOB_STATUS" == "success" ]; then | |
| EMOJI="✅" | |
| TITLE="LT Rebase Merge completed successfully" | |
| MESSAGE="Successfully completed LT $LT_VERSION rebase merge" | |
| else | |
| EMOJI="❌" | |
| TITLE="LT Rebase Merge failed" | |
| MESSAGE="Failed to complete LT $LT_VERSION rebase merge. Please check the workflow run for details." | |
| fi | |
| if gh pr comment "$PR_NUMBER" \ | |
| --body "$EMOJI **$TITLE** | |
| $MESSAGE | |
| Workflow run: $RUN_URL" \ | |
| --repo "$REPOSITORY"; then | |
| echo "✅ Successfully commented on PR #$PR_NUMBER" | |
| else | |
| echo "❌ Failed to comment on PR #$PR_NUMBER" | |
| echo "Comment posting failure is non-fatal, but PR status may not be visible" | |
| fi |