| # Workflow that runs periodically to check whether PRs have been imported into |
| # Gerrit and if they have been merged. The workflow adds a PR comment after |
| # import and after merge. PRs that are merged upstream are then closed. |
| name: PR Watcher |
| on: |
| workflow_dispatch: |
| schedule: |
| - cron: "*/10 * * * *" # Every 10 minutes |
| |
| jobs: |
| check: |
| name: Check Copybara Import Status |
| runs-on: ubuntu-latest |
| permissions: |
| actions: write |
| steps: |
| - name: Check Pull Requests |
| uses: actions/github-script@v6 |
| with: |
| script: | |
| // Get all open PRs. |
| const pulls = await github.rest.pulls.list({ |
| owner: context.repo.owner, |
| repo: context.repo.repo, |
| state: 'open', |
| base: 'main', |
| }); |
| |
| for (const pull of pulls.data) { |
| const query = `query($owner:String!, $name:String!, $pullNumber:Int!, $statusContext:String!) { |
| repository(owner:$owner, name:$name) { |
| pullRequest(number:$pullNumber) { |
| commits(last:1) { |
| nodes { |
| commit { |
| status { |
| context(name: $statusContext) { |
| state |
| targetUrl |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| }`; |
| |
| // Query the import/copybara status check. |
| const result = await github.graphql(query, { |
| owner: context.repo.owner, |
| name: context.repo.repo, |
| pullNumber: pull.number, |
| statusContext: 'import/copybara', |
| }); |
| const nodes = result.repository.pullRequest.commits.nodes; |
| const commit = nodes[0].commit; |
| if (commit && commit.status && commit.status.context) { |
| // Forward the status check information to the pr-manager workflow. |
| await github.rest.actions.createWorkflowDispatch({ |
| owner: context.repo.owner, |
| repo: context.repo.repo, |
| ref: 'main', |
| workflow_id: 'pr-manager.yml', |
| inputs: { |
| pullNumber: pull.number.toString(), |
| state: commit.status.context.state, |
| targetUrl: commit.status.context.targetUrl, |
| }, |
| }); |
| } |
| |
| // Look through all timeline events on the PR. |
| // When the PR is merged upstream, it will include a backreference back to |
| // the PR in the commit message. |
| const timeline = github.paginate.iterator(github.rest.issues.listEventsForTimeline, { |
| owner: context.repo.owner, |
| repo: context.repo.repo, |
| issue_number: pull.number, |
| }); |
| for await (const { data: timelineData } of timeline) { |
| for (const timelineItem of timelineData) { |
| if (timelineItem.event === 'referenced' && timelineItem.commit_id) { |
| try { |
| // Compare commit ids against refs/heads/main to see if they are |
| // included in the main branch. |
| const compare = await github.rest.repos.compareCommitsWithBasehead({ |
| basehead: `${timelineItem.commit_id}...refs/heads/main`, |
| owner: context.repo.owner, |
| repo: context.repo.repo, |
| }); |
| if (compare.data.status == "ahead" || compare.data.status == "identical") { |
| // This is a commit that has already been merged into main. |
| const commit = await github.rest.repos.getCommit({ |
| owner: context.repo.owner, |
| repo: context.repo.repo, |
| ref: timelineItem.commit_id, |
| }); |
| const message = commit.data.commit.message; |
| const lines = message.split('\n'); |
| for (const line of lines) { |
| // Check if the commit has a footer referencing a PR. |
| const tag = 'GITHUB_PR_HEAD_SHA='; |
| if (line.startsWith(tag)) { |
| const sha = line.slice(tag.length); |
| // The merged commit footer matches the PR sha. |
| if (pull.head.sha === sha) { |
| // Close the PR. |
| await github.rest.actions.createWorkflowDispatch({ |
| owner: context.repo.owner, |
| repo: context.repo.repo, |
| ref: 'main', |
| workflow_id: 'pr-manager.yml', |
| inputs: { |
| pullNumber: pull.number.toString(), |
| state: 'merged', |
| targetUrl: commit.data.html_url, |
| }, |
| }); |
| } |
| } |
| } |
| } |
| } catch (err) { |
| // This is OK because not all commits will be comparable. |
| console.warn(err); |
| } |
| } |
| } |
| } |
| } |