我是基于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 能够:
- 检测是否是 Dependabot 发起的 PR
- 能否通过某些特定条件(例如单元测试,网页托管部署等)
- 如果符合上述条件,就自动合并 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 test
或 mvn 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