Download .github/workflows/pr-artifacts.yml from SaylorTwift/openhands: direct link, hf CLI and curl.
- Browser
- Download file 13.8 kB
-
https://huggingface.co/SaylorTwift/openhands/resolve/main/.github/workflows/pr-artifacts.yml
- Command line
-
hf download hf://SaylorTwift/openhands/.github/workflows/pr-artifacts.yml
-
curl -L -o pr-artifacts.yml https://huggingface.co/SaylorTwift/openhands/resolve/main/.github/workflows/pr-artifacts.yml
13.8 kB
| name: PR Artifacts | |
| on: | |
| workflow_dispatch: | |
| pull_request_target: | |
| types: [opened, synchronize, reopened, closed] | |
| branches: | |
| - main | |
| pull_request_review: | |
| types: [submitted] | |
| jobs: | |
| cleanup-on-approval: | |
| if: github.event_name == 'pull_request_review' && github.event.review.state == 'approved' | |
| runs-on: ubuntu-latest | |
| concurrency: | |
| group: pr-artifacts-live-e2e-${{ github.event.pull_request.number }} | |
| cancel-in-progress: false | |
| permissions: | |
| contents: write | |
| issues: write | |
| pull-requests: write | |
| steps: | |
| - name: Check if fork PR | |
| id: check_fork | |
| run: | | |
| if [ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.event.pull_request.base.repo.full_name }}" ]; then | |
| echo "is_fork=true" >> "$GITHUB_OUTPUT" | |
| echo "::notice::Fork PR detected - skipping automatic .pr cleanup." | |
| else | |
| echo "is_fork=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Resolve PR context | |
| id: pr_context | |
| if: steps.check_fork.outputs.is_fork == 'false' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| pr_api="repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}" | |
| head_ref="$(gh api "$pr_api" --jq '.head.ref')" | |
| head_sha="$(gh api "$pr_api" --jq '.head.sha')" | |
| echo "head_ref=$head_ref" >> "$GITHUB_OUTPUT" | |
| echo "head_sha=$head_sha" >> "$GITHUB_OUTPUT" | |
| if gh api "repos/${{ github.repository }}/contents/.pr?ref=$head_sha" --silent >/dev/null 2>&1; then | |
| echo "has_pr_dir=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "has_pr_dir=false" >> "$GITHUB_OUTPUT" | |
| echo "::notice::No .pr directory exists at the approved PR head; skipping artifact cleanup." | |
| fi | |
| - name: Check out PR branch | |
| if: steps.check_fork.outputs.is_fork == 'false' && steps.pr_context.outputs.has_pr_dir == 'true' | |
| env: | |
| PR_ARTIFACT_PUSH_TOKEN: ${{ secrets.OPENHANDS_BOT_GITHUB_PAT_PUBLIC || github.token }} | |
| PR_HEAD_SHA: ${{ steps.pr_context.outputs.head_sha }} | |
| run: | | |
| set -euo pipefail | |
| git init . | |
| git remote add origin "https://github.com/${GITHUB_REPOSITORY}.git" | |
| git fetch origin "$PR_HEAD_SHA" | |
| git checkout --detach FETCH_HEAD | |
| actual_sha="$(git rev-parse HEAD)" | |
| if [ "$actual_sha" != "$PR_HEAD_SHA" ]; then | |
| echo "::error::SHA mismatch: expected $PR_HEAD_SHA, got $actual_sha" | |
| exit 1 | |
| fi | |
| git config credential.helper '!f() { echo "username=x-access-token"; echo "password=$PR_ARTIFACT_PUSH_TOKEN"; }; f' | |
| - name: Remove .pr directory | |
| id: remove | |
| if: steps.check_fork.outputs.is_fork == 'false' && steps.pr_context.outputs.has_pr_dir == 'true' | |
| env: | |
| PR_ARTIFACT_PUSH_TOKEN: ${{ secrets.OPENHANDS_BOT_GITHUB_PAT_PUBLIC || github.token }} | |
| PR_HEAD_REF: ${{ steps.pr_context.outputs.head_ref }} | |
| PR_HEAD_SHA: ${{ steps.pr_context.outputs.head_sha }} | |
| run: | | |
| set -euo pipefail | |
| if [ ! -d ".pr" ]; then | |
| echo "removed=false" >> "$GITHUB_OUTPUT" | |
| echo "::notice::No .pr directory to remove." | |
| exit 0 | |
| fi | |
| git config user.name "allhands-bot" | |
| git config user.email "allhands-bot@users.noreply.github.com" | |
| git rm -rf --ignore-unmatch .pr/ | |
| if git diff --cached --quiet; then | |
| echo "removed=false" >> "$GITHUB_OUTPUT" | |
| echo "::notice::No tracked .pr files to remove." | |
| exit 0 | |
| fi | |
| git commit -m "chore: Remove PR-only artifacts" | |
| push_succeeded=false | |
| for attempt in 1 2 3; do | |
| if git push origin "HEAD:refs/heads/$PR_HEAD_REF"; then | |
| push_succeeded=true | |
| break | |
| fi | |
| if [ "$attempt" -lt 3 ]; then | |
| echo "::notice::Failed to push cleanup commit, rebasing and retrying." | |
| git fetch origin "$PR_HEAD_REF" | |
| if ! git rebase FETCH_HEAD; then | |
| echo "::error::Failed to rebase .pr cleanup commit. Manual resolution required." | |
| exit 1 | |
| fi | |
| if ! git merge-base --is-ancestor "$PR_HEAD_SHA" HEAD; then | |
| echo "::error::Rebased .pr cleanup commit no longer descends from the approved PR head $PR_HEAD_SHA." | |
| exit 1 | |
| fi | |
| sleep 2 | |
| fi | |
| done | |
| if [ "$push_succeeded" != "true" ]; then | |
| echo "::error::Failed to push .pr cleanup commit after retries." | |
| exit 1 | |
| fi | |
| echo "removed=true" >> "$GITHUB_OUTPUT" | |
| - name: Update PR artifacts comment | |
| if: steps.check_fork.outputs.is_fork == 'false' && steps.pr_context.outputs.has_pr_dir == 'true' && steps.remove.outputs.removed == 'true' | |
| uses: actions/github-script@v9 | |
| with: | |
| script: | | |
| const marker = '<!-- pr-artifacts-notice -->'; | |
| const body = [ | |
| marker, | |
| '**PR Artifacts Cleaned Up**', | |
| '', | |
| 'The `.pr/` directory has been removed after approval.', | |
| '', | |
| ].join('\n'); | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| const existing = comments.find((comment) => comment.body.includes(marker)); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existing.id, | |
| body, | |
| }); | |
| } | |
| check-pr-artifacts: | |
| if: github.event_name == 'pull_request_target' && github.event.action != 'closed' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| issues: write | |
| pull-requests: write | |
| steps: | |
| - name: Post or update PR artifacts comment | |
| uses: actions/github-script@v9 | |
| with: | |
| script: | | |
| const marker = '<!-- pr-artifacts-notice -->'; | |
| const pullRequest = context.payload.pull_request; | |
| const headRepository = pullRequest.head.repo; | |
| let exists = true; | |
| try { | |
| await github.rest.repos.getContent({ | |
| owner: headRepository.owner.login, | |
| repo: headRepository.name, | |
| path: '.pr', | |
| ref: pullRequest.head.sha, | |
| }); | |
| } catch (error) { | |
| if (error.status === 404) { | |
| exists = false; | |
| } else { | |
| throw error; | |
| } | |
| } | |
| const comments = await github.paginate( | |
| github.rest.issues.listComments, | |
| { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| per_page: 100, | |
| }, | |
| ); | |
| const existing = comments.find((comment) => comment.body.includes(marker)); | |
| if (!exists) { | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existing.id, | |
| body: [ | |
| marker, | |
| '**PR Artifacts Cleaned Up**', | |
| '', | |
| 'The `.pr/` directory is no longer present.', | |
| '', | |
| ].join('\n'), | |
| }); | |
| } | |
| return; | |
| } | |
| const isFork = headRepository.full_name !== context.payload.repository.full_name; | |
| const cleanup = isFork | |
| ? 'Because this is a fork PR, the workflow will **open or update a cleanup PR against `main` after merge**.' | |
| : 'The directory will be **automatically removed when the PR is approved**.'; | |
| const body = [ | |
| marker, | |
| '**PR Artifacts Notice**', | |
| '', | |
| 'This PR contains a `.pr/` directory with temporary PR-specific documents. ' + cleanup, | |
| '', | |
| ].join('\n'); | |
| core.warning('.pr/ directory contains temporary PR-only artifacts'); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existing.id, | |
| body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body, | |
| }); | |
| } | |
| # Fork branches cannot be modified reliably. If artifacts reach the trusted base, | |
| # remove them through a pull request so branch protections remain enforced. | |
| cleanup-after-merge: | |
| concurrency: | |
| group: pr-artifacts-live-e2e-${{ github.event.pull_request.base.ref }} | |
| cancel-in-progress: false | |
| if: >- | |
| github.event_name == 'pull_request_target' && | |
| github.event.action == 'closed' && | |
| github.event.pull_request.merged == true | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| issues: write | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| ref: ${{ github.event.pull_request.base.ref }} | |
| token: ${{ secrets.OPENHANDS_BOT_GITHUB_PAT_PUBLIC }} | |
| - name: Create or update cleanup PR | |
| id: cleanup | |
| env: | |
| BASE_REF: ${{ github.event.pull_request.base.ref }} | |
| CLEANUP_BRANCH: automation/remove-pr-artifacts | |
| GH_TOKEN: ${{ secrets.OPENHANDS_BOT_GITHUB_PAT_PUBLIC }} | |
| run: | | |
| set -euo pipefail | |
| if [ ! -d ".pr" ]; then | |
| echo "created=false" >> "$GITHUB_OUTPUT" | |
| echo "::notice::No .pr/ directory to remove from $BASE_REF" | |
| exit 0 | |
| fi | |
| git config user.name "allhands-bot" | |
| git config user.email "allhands-bot@users.noreply.github.com" | |
| git fetch origin \ | |
| "+refs/heads/$CLEANUP_BRANCH:refs/remotes/origin/$CLEANUP_BRANCH" || true | |
| git checkout -B "$CLEANUP_BRANCH" | |
| git rm -rf --ignore-unmatch .pr/ | |
| if git diff --cached --quiet; then | |
| echo "created=false" >> "$GITHUB_OUTPUT" | |
| echo "::notice::No tracked .pr/ files to remove from $BASE_REF" | |
| exit 0 | |
| fi | |
| git commit \ | |
| -m "chore: remove merged PR artifacts" \ | |
| -m "Co-authored-by: openhands <openhands@all-hands.dev>" | |
| git push --force-with-lease origin "HEAD:$CLEANUP_BRANCH" | |
| cleanup_pr_url=$(gh pr list \ | |
| --base "$BASE_REF" \ | |
| --head "$CLEANUP_BRANCH" \ | |
| --state open \ | |
| --json url \ | |
| --jq '.[0].url') | |
| if [ -z "$cleanup_pr_url" ]; then | |
| cleanup_pr_url=$(gh pr create \ | |
| --base "$BASE_REF" \ | |
| --head "$CLEANUP_BRANCH" \ | |
| --title "chore: remove merged PR artifacts" \ | |
| --body-file - <<EOF | |
| ## Why | |
| Temporary \`.pr/\` artifacts reached \`$BASE_REF\` through one or more merged PRs. | |
| ## Summary | |
| - Remove the temporary \`.pr/\` directory from \`$BASE_REF\`. | |
| ## How to Test | |
| - Confirm this PR only deletes files under \`.pr/\`. | |
| - Let the required checks validate the cleanup commit. | |
| --- | |
| _This PR was automatically created by the PR Artifacts workflow._ | |
| EOF | |
| ) | |
| fi | |
| echo "created=true" >> "$GITHUB_OUTPUT" | |
| echo "pr_url=$cleanup_pr_url" >> "$GITHUB_OUTPUT" | |
| echo "::notice::Cleanup PR ready: $cleanup_pr_url" | |
| - name: Update source PR comment | |
| if: steps.cleanup.outputs.created == 'true' | |
| uses: actions/github-script@v9 | |
| env: | |
| CLEANUP_PR_URL: ${{ steps.cleanup.outputs.pr_url }} | |
| with: | |
| script: | | |
| const marker = '<!-- pr-artifacts-notice -->'; | |
| const body = [ | |
| marker, | |
| '**PR Artifact Cleanup Queued**', | |
| '', | |
| 'The `.pr/` directory reached `' + context.payload.pull_request.base.ref + '` after merge. A cleanup PR has been opened or updated: ' + process.env.CLEANUP_PR_URL, | |
| '', | |
| ].join('\n'); | |
| const comments = await github.paginate( | |
| github.rest.issues.listComments, | |
| { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| per_page: 100, | |
| }, | |
| ); | |
| const existing = comments.find((comment) => comment.body.includes(marker)); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existing.id, | |
| body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body, | |
| }); | |
| } | |