我是基于ChatGPT-turbo-3.5实现的AI助手,在此网站上负责整理和概括文章

文章介绍了如何使用GitHub Action编写CI来自动合并Dependabot发起的PR。首先定义了任务,然后编写了判断条件,最后实现了自动合并PR的逻辑。文章以一个博客网站部署到Cloudflare为例,说明了如何等待部署完成后自动合并PR,或者通过手动评论`try`来触发CI自动合并。最后给出了完整的CI代码示例。整体内容简洁明了,展示了如何利用CI工具提高工作效率。

# 开篇

众所周知,把项目部署到 GitHub 后,可以使用 Dependabot 来定期更新项目使用到的第三方库,例如自动更新 C# 项目里 .csproj 里引用到的库,又例如 NodeJs 项目里的 node_modules 里的第三方库

但是!!

有的第三方库更新的特别频繁,导致 Dependabot 会一直发起新的 PR,如果每次都需要自己手动合并,那就真的要吐血了

所以,本文会包含如何写一个 GitHub 上的 Action(也就是俗称的 CI)去自动合并 Dependabot 发起的 PR,并且还会稍作修改,变成达成某些特定条件再去合并

# 自动合并的 PR 的 CI

我们希望这个 CI 能够:

  1. 检测是否是 Dependabot 发起的 PR
  2. 能否通过某些特定条件(例如单元测试,网页托管部署等)
  3. 如果符合上述条件,就自动合并 PR 到主分支

注意,本文的 ${ xxx } 实际上是 ${{ xxx }} ,生成代码块的时候 ${{}} 会变成特殊符号所以只能删去一对大括号!

# 定义任务

我们先粗略编写一个 CI 的配置文件:

name: Auto-merge Dependabot PR after Deployment
on:
  pull_request:
    types:
      - opened
      - synchronize
  issue_comment:
    types:
      - created
jobs:
  wait-for-cloudflare-deploy:
    if: |
      (github.event_name == 'pull_request' && (github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]')) ||
      (github.event.comment.body == 'try' && github.event.issue.pull_request != null)
    runs-on: ubuntu-latest
    timeout-minutes: 20

这个文件会在创建新的和同步一个 PR 的时候触发,或者在有人评论的时候触发(用于手动触发 CI 来调试和重新跑 CI,当然也可以直接重新 Run CI,但是这样的话没办法重新执行最新的 CI 代码)

这个 CI 配置里定义了一个任务,在这里我的任务是等待 Cloudflare 部署我的这个网站,是的,我把我的网站托管到 Cloudflare 了

这个任务的触发条件是必须由 Dependabot 发起 PR,或者任意一人(当然也可以限制为仓库维护者)在 PR 里评论 try

这个任务最多运行 20 分钟,同时运行在 GitHub 最新的 ubuntu runner 上

如果不知道上面在说啥,建议先了解一下 CI 的基础,这里不进行讲解

# 合并判定

现在我们可以执行自己的逻辑,来检测是否要合并这个 PR 了:

例如 C# 项目可以写:

steps:
      - name: Checkout code
        uses: actions/checkout@v3
      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: 8.0.x
      - name: Restore dependencies
        run: dotnet restore C#项目.csproj
      - name: Build
        run: dotnet build C#项目.csproj
      - name: Test
        id: check
        run: dotnet test C#项目.csproj

Java 项目也同理,初始化 Java 环境后使用 ./gradlew testmvn test 即可,记得一定要给判定是否合并 PR 的前置任务加上 id: check

比如这个博客网站这里,因为我是要检测是否成功在 Cloudflare 上部署,我写了如下代码:

steps:
      - name: Checkout code
        uses: actions/checkout@v3
      - name: Poll Cloudflare Deployment Status
        id: check
        env:
          ACC_ID: ${ secrets.ACCOUNT_ID }
          PROJ: ${ secrets.PROJECT_NAME }
          TOKEN: ${ secrets.API_TOKEN }
        run: |
          DEPLOY_STATUS="idle"
          LATEST_STAGE_NAME="queued"
          
          # Wait until the latest stage is "deploy"
          while [[ "$LATEST_STAGE_NAME" != "deploy" ]]; do
            echo "Waiting for the 'deploy' stage..."
            LATEST_STAGE_NAME=$(curl -s -X GET "https://api.cloudflare.com/client/v4/accounts/$ACC_ID/pages/projects/$PROJ/deployments?env=preview" \
                -H "Authorization: Bearer $TOKEN" \
                -H "Content-Type: application/json" | jq -r '.result[0].latest_stage.name')
            echo "Current stage: $LATEST_STAGE_NAME"
            sleep 5
          done
          
          # Once in deploy stage, check the deployment status
          while [[ "$DEPLOY_STATUS" == "idle" || "$DEPLOY_STATUS" == "active" ]]; do
            echo "Checking Cloudflare deployment status..."
            DEPLOY_STATUS=$(curl -s -X GET "https://api.cloudflare.com/client/v4/accounts/$ACC_ID/pages/projects/$PROJ/deployments" \
                -H "Authorization: Bearer $TOKEN" \
                -H "Content-Type: application/json" | jq -r '.result[0].latest_stage.status')
            echo "Deployment status: $DEPLOY_STATUS"
            
            if [[ "$DEPLOY_STATUS" == "failure" || "$DEPLOY_STATUS" == "canceled" ]]; then
              echo "Deployment failed. Exiting."
              exit 1
            fi
            
            sleep 5
          done

我通过 GitHub Secrets 存了 Cloudflare 里的敏感数据,例如账号 ID,项目名,API Token,同时我利用 Cloudflare 的 API 去检测页面是否成功生成并部署

# 合并 PR

check 任务的判断成功后,我们就可以进行合并 PR 了,我们先写 Dependabot 发起的 PR 时触发 CI 后进行合并的策略:

因为我们在上一部分定义了 steps 了,我们只需要在 check 任务后面继续添加任务即可

- name: Merge the Dependabot PR
        if: steps.check.outcome == 'success' && github.event_name == 'pull_request' && (github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]')
        run: |
          gh pr merge ${ github.event.pull_request.number } --squash --auto --repo ${ github.repository }
        env:
          GITHUB_TOKEN: ${secrets.GITHUB_TOKEN }

这样的话只要 check 任务成功,并且这个任务是由 Dependabot 发起 PR 触发的,CI 就会自动获取项目管理者的 GITHUB_TOKEN 去合并 PR

接下来,我们处理一下手动评论 try 来触发 CI 时的 PR 合并策略:

- name: Merge the PR if comment is "try"
        if: steps.check.outcome == 'success' && github.event.comment.body == 'try' && github.event.issue.pull_request != null
        run: |
          echo "Merging the PR because deploy succeeded and comment is 'try'."
          gh pr merge ${ github.event.issue.number } --squash --auto --repo ${ github.repository }
        env:
          GITHUB_TOKEN: ${ secrets.GITHUB_TOKEN }

同样的道理,我们这里需要 check 任务成功执行,然后判断评论的内容是不是 try ,以及是不是在一个 PR 内评论,是的话我们就进行合并 PR,与上面的不同之处是这里我们使用 github.event.issue.number 去合并 PR

如果项目不是 Private 的,建议再给通过评论触发 CI 添加一些限制(例如只有项目管理员才能触发合并 PR 的 CI)

# 完整代码示例

下面是本博客用到的 CI,会在 Dependabot 发起 PR 时检测 Cloudflare 页面部署状态,部署成功后自动合并,或者在有人在某 PR 评论 try 时触发检测并尝试合并,原理就是上面提到的内容

name: Auto-merge Dependabot PR after Deployment
on:
  pull_request:
    types:
      - opened
      - synchronize
  issue_comment:
    types:
      - created
jobs:
  wait-for-cloudflare-deploy:
    if: |
      (github.event_name == 'pull_request' && (github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]')) ||
      (github.event.comment.body == 'try' && github.event.issue.pull_request != null)
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      - name: Poll Cloudflare Deployment Status
        id: check_deploy
        env:
          ACC_ID: ${ secrets.ACCOUNT_ID }
          PROJ: ${ secrets.PROJECT_NAME }
          TOKEN: ${ secrets.API_TOKEN }
        run: |
          DEPLOY_STATUS="idle"
          LATEST_STAGE_NAME="queued"
          
          # Wait until the latest stage is "deploy"
          while [[ "$LATEST_STAGE_NAME" != "deploy" ]]; do
            echo "Waiting for the 'deploy' stage..."
            LATEST_STAGE_NAME=$(curl -s -X GET "https://api.cloudflare.com/client/v4/accounts/$ACC_ID/pages/projects/$PROJ/deployments?env=preview" \
                -H "Authorization: Bearer $TOKEN" \
                -H "Content-Type: application/json" | jq -r '.result[0].latest_stage.name')
            echo "Current stage: $LATEST_STAGE_NAME"
            sleep 5
          done
          
          # Once in deploy stage, check the deployment status
          while [[ "$DEPLOY_STATUS" == "idle" || "$DEPLOY_STATUS" == "active" ]]; do
            echo "Checking Cloudflare deployment status..."
            DEPLOY_STATUS=$(curl -s -X GET "https://api.cloudflare.com/client/v4/accounts/$ACC_ID/pages/projects/$PROJ/deployments" \
                -H "Authorization: Bearer $TOKEN" \
                -H "Content-Type: application/json" | jq -r '.result[0].latest_stage.status')
            echo "Deployment status: $DEPLOY_STATUS"
            
            if [[ "$DEPLOY_STATUS" == "failure" || "$DEPLOY_STATUS" == "canceled" ]]; then
              echo "Deployment failed. Exiting."
              exit 1
            fi
            
            sleep 5
          done
      - name: Merge the Dependabot PR
        if: steps.check_deploy.outcome == 'success' && github.event_name == 'pull_request' && (github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]')
        run: |
          gh pr merge ${ github.event.pull_request.number } --squash --auto --repo ${ github.repository }
        env:
          GITHUB_TOKEN: ${ secrets.GITHUB_TOKEN }
          
      - name: Merge the PR if comment is "try"
        if: steps.check_deploy.outcome == 'success' && github.event.comment.body == 'try' && github.event.issue.pull_request != null
        run: |
          echo "Merging the PR because deploy succeeded and comment is 'try'."
          gh pr merge ${ github.event.issue.number } --squash --auto --repo ${ github.repository }
        env:
          GITHUB_TOKEN: ${ secrets.GITHUB_TOKEN }

# 总结

其实这东西每次收到邮件手动点一点 merge 就行,主要是我仓库太多然后很多时候懒得去管,时间久了会堆积几十个 PR,看着头大,不如搞个自动化工具替我合并这些 PR,于是倒腾了一下搞了个这个 CI

此文章已被阅读次数:正在加载...更新于

请我喝[茶]~( ̄▽ ̄)~*

Jason Xu 微信支付

微信支付

Jason Xu 支付宝

支付宝