Oasis's Cloud

一个人的首要责任,就是要有雄心。雄心是一种高尚的激情,它可以采取多种合理的形式。
—— 《一个数学家的辩白》

在 GitHub Action 中获取改动文件


在 GitHub Action 中可以通过下面的两种方式来获得变更的文件:

  1. 在仓库下通过使用 git 命令对比两个分支。
  2. 借助 GitHub 提供的 API 来查询变更的文件。

这篇文章主要介绍第二种方法,我采用第二种方式实现了获取 Pull Request 中的变更文件,针对变更的文件进行单元测试。

GitHub 提供了 /repos/{owner}/{repo}/pulls/{pull_number}/files API 用于查询 Pull Request 的相关内容。

想要使用此 API ,我们需要提供 owner、repo、pull number 这三个参数的内容。owner、repo 和 pull number 可以通过 GitHub Action 中的上下文来获取:

name: CI

on:
  push:
    branches:
      - main

  pull_request:
    branches:
      - main

  workflow_dispatch:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Get PR info
        run: |
          echo "PR Message github.event.number: ${{ github.event.number }}"
          echo "PR Message github.repository: ${{ github.repository }}"

这里给出了一段 GitHub Action 的脚本,在 Get PR info 任务中,借助 linux 的 echo 命令,打印来 github.event.number 和 github.repository。想要对这段脚本进行测试,可将其提交到 .github/workflows/test.yml 中,然后通过其他分支向目标分支创建 PR。(备注:github.event.number来自Webhook 的共有属性

通过执行上述代码,我们可以看到输出的 PR 的 ID 和 repository。接下来要使用他们调用 GitHub 提供的 API。这里我采用了 curl 来进行 GitHub API 的调用:

pr_number=${{ github.event.number }}
files_url="https://api.github.com/repos/${{ github.repository }}/pulls/$pr_number/files"
files_response=$(curl -sSL -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" $files_url)

在这段代码中即存在 ${{ xx }} 又存在 $xx,我们需要先区分什么时候使用什么形式。${{}} 是 GitHub 提供的插值表达式,在 GitHub Actions 执行的时候会对这种表达式进行求值。简单理解的话,就是将表达式计算出的结果填到脚本中,然后在执行脚本。$xx 是 Linux shell 的变量调用的方式,所以在 GitHub Actions 中我们实际执行的主要是 Linux shell 脚本。

在这段代码中,声明变量的方式形如:

shellVariable=1

调用变量的方式形如:

echo "$shellVariable"

到这里我们已经获取到了 GitHub API 返回的结果,接下来需要对结果进行处理,即提取变更文件。

files_changed=$(echo "$files_response" | jq -r '.[].filename')
echo "PR files: $files_changed"
component=$(echo "$files_changed" |  grep -E -i 'src\/packages\/([a-z]+)(\/[a-z_\.]*)*' | sed -E 's/src\/packages\/([a-z]+)(\/[a-z_\.]*)*/\1/i' | awk 'END{print}')
npm test -- $component

因为 GitHub API 返回的是 JSON 格式:

[
  {
    "sha": "bbcd538c8e72b8c175046e27cc8f907076331401",
    "filename": "src/packages/button/button.tsx",
    "status": "added",
    "additions": 103,
    "deletions": 21,
    "changes": 124,
    "blob_url": "https://github.com/octocat/Hello-World/blob/6dcb09b5b57875f334f61aebed695e2e4193db5e/file1.txt",
    "raw_url": "https://github.com/octocat/Hello-World/raw/6dcb09b5b57875f334f61aebed695e2e4193db5e/file1.txt",
    "contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/file1.txt?ref=6dcb09b5b57875f334f61aebed695e2e4193db5e",
    "patch": "@@ -132,7 +132,7 @@ module Test @@ -1000,7 +1000,7 @@ module Test"
  }
]

所以我们通过 jq 从 GitHub API 返回的结果中提取我们需要的数据,即第一行代码中的 jq -r '.[].filename,这句代码的意思是取 filename 字段的值,然后以原始文本形式返回。对于上面的 JSON 数据,jq 的处理结果是:src/packages/button/button.tsx。(jq 介绍

获取变更文件后,还需要将src/packages/button/button.tsx中的 button 提取出来,以便传递给 npm 脚本。对于变更文件的处理,主要借助 Linux 中的 grep、sed、awk。

grep -E -i 表示使用正则表达式,并且忽略大小写。 sed -E 同样表示使用正则表达式,但是它的忽略大小写是写到后面的参数中。

通过这些步骤,就可以提取 PR 中的变更文件,对文件进行过滤,然后针对特定文件执行特定的处理。

总的来说,GitHub 提供了 Actions 来帮助我们做自动化的处理,并且允许我们通过 yml 格式的脚本来进行配置,针对 yml 的脚本,GitHub 提供了内置的变量,通过插值方式提供给开发者使用,而且 run 命令提供给开发者使用 shell 的便捷性。