diff --git a/.github/workflows/style-bot-action.yml b/.github/workflows/style-bot-action.yml new file mode 100644 index 0000000000..be3146a4b4 --- /dev/null +++ b/.github/workflows/style-bot-action.yml @@ -0,0 +1,175 @@ +name: Style Bot Action + +on: + workflow_call: + inputs: + pre_commit_script: + required: false + type: string + description: "Optional script to run before committing changes" + pre_commit_script_name: + required: false + type: string + description: "Custom name for the pre-commit script step" + default: "Custom pre-commit script" + python_quality_dependencies: + required: true + type: string + description: "Python package extras to install for quality checks (e.g. '[quality]')" + python_version: + required: false + type: string + description: "Python version to run code formatter" + default: "3.10" + style_command: + required: false + type: string + description: "Command to run for style checks or/and style fixes" + default: "make style && make quality" + secrets: + bot_token: + required: true + description: "GitHub token with permissions to comment and push to PR" + +jobs: + check-permissions: + if: > + contains(github.event.comment.body, '@bot /style') && + github.event.issue.pull_request != null + runs-on: ubuntu-latest + outputs: + is_authorized: ${{ steps.check_user_permission.outputs.has_permission }} + steps: + - name: Check user permission + id: check_user_permission + uses: actions/github-script@v6 + with: + script: | + const comment_user = context.payload.comment.user.login; + const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({ + owner: context.repo.owner, + repo: context.repo.repo, + username: comment_user + }); + const authorized = permission.permission === 'admin'; + console.log(`User ${comment_user} has permission level: ${permission.permission}, authorized: ${authorized} (only admins allowed)`); + core.setOutput('has_permission', authorized); + + run-style-bot: + needs: check-permissions + if: needs.check-permissions.outputs.is_authorized == 'true' + runs-on: ubuntu-latest + steps: + - name: Extract PR details + id: pr_info + uses: actions/github-script@v6 + with: + script: | + const prNumber = context.payload.issue.number; + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber + }); + + // We capture both the branch ref and the "full_name" of the head repo + // so that we can check out the correct repository & branch (including forks). + core.setOutput("prNumber", prNumber); + core.setOutput("headRef", pr.head.ref); + core.setOutput("headRepoFullName", pr.head.repo.full_name); + + - name: Check out PR branch + uses: actions/checkout@v3 + env: + HEADREPOFULLNAME: ${{ steps.pr_info.outputs.headRepoFullName }} + HEADREF: ${{ steps.pr_info.outputs.headRef }} + with: + # Instead of checking out the base repo, use the contributor's repo name + repository: ${{ env.HEADREPOFULLNAME }} + ref: ${{ env.HEADREF }} + # You may need fetch-depth: 0 for being able to push + fetch-depth: 0 + token: ${{ secrets.bot_token }} + + - name: Debug + env: + HEADREPOFULLNAME: ${{ steps.pr_info.outputs.headRepoFullName }} + HEADREF: ${{ steps.pr_info.outputs.headRef }} + PRNUMBER: ${{ steps.pr_info.outputs.prNumber }} + run: | + echo "PR number: $PRNUMBER" + echo "Head Ref: $HEADREF" + echo "Head Repo Full Name: $HEADREPOFULLNAME" + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ inputs.python_version }} + + - name: Install dependencies + env: + python_quality_dependencies: ${{ inputs.python_quality_dependencies }} + run: | + python -m pip install --upgrade pip + pip install .$python_quality_dependencies + + - name: ${{ inputs.pre_commit_script_name }} + env: + pre_commit_script: ${{ inputs.pre_commit_script }} + if: inputs.pre_commit_script != '' + shell: bash + run: | + echo "$pre_commit_script" > pre_commit_script.sh + chmod +x pre_commit_script.sh + ./pre_commit_script.sh + + - name: Run make style and make quality + env: + style_command: ${{ inputs.style_command }} + run: | + $style_command + + - name: Commit and push changes + id: commit_and_push + env: + HEADREPOFULLNAME: ${{ steps.pr_info.outputs.headRepoFullName }} + HEADREF: ${{ steps.pr_info.outputs.headRef }} + PRNUMBER: ${{ steps.pr_info.outputs.prNumber }} + GITHUB_TOKEN: ${{ secrets.bot_token }} + run: | + echo "HEADREPOFULLNAME: $HEADREPOFULLNAME, HEADREF: $HEADREF" + # Configure git with the Actions bot user + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + # Make sure your 'origin' remote is set to the contributor's fork + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/$HEADREPOFULLNAME.git" + + # If there are changes after running style/quality, commit them + if [ -n "$(git status --porcelain)" ]; then + git add . + git commit -m "Apply style fixes" + # Push to the original contributor's forked branch + git push origin HEAD:$HEADREF + echo "changes_pushed=true" >> $GITHUB_OUTPUT + else + echo "No changes to commit." + echo "changes_pushed=false" >> $GITHUB_OUTPUT + fi + + - name: Comment on PR with workflow run link + if: steps.commit_and_push.outputs.changes_pushed == 'true' + uses: actions/github-script@v6 + with: + script: | + const prNumber = parseInt(process.env.prNumber, 10); + const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}` + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: `Style fixes have been applied. [View the workflow run here](${runUrl}).` + }); + env: + prNumber: ${{ steps.pr_info.outputs.prNumber }} \ No newline at end of file diff --git a/.github/workflows/style-bot.yml b/.github/workflows/style-bot.yml new file mode 100644 index 0000000000..1f01855447 --- /dev/null +++ b/.github/workflows/style-bot.yml @@ -0,0 +1,18 @@ +name: Style Bot + +on: + issue_comment: + types: [created] + +permissions: + contents: write + pull-requests: write + +jobs: + style: + uses: ./.github/workflows/style-bot-action.yml + with: + python_quality_dependencies: "[quality]" + style_command: "make style" + secrets: + bot_token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file