[CLK] Embed dist-git in source branch #24
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@v1 | |
| with: | |
| app-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@v4 | |
| 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@v4 | |
| 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: Setup branch references and execute lt_rebase_merge.sh | |
| 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: 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 |