青春是有限的,智慧是无穷的,趁短的青春,去学习无穷的智慧。—— 高尔基

actions/checkout:让工作流真正拿到代码的那一步

在自动化工作流里,代码并不会天然出现在运行环境中。

一次工作流被触发后,Runner 已经准备就绪,后续的构建、测试、打包、检查与发布步骤也许都已写好,但它们仍然需要先面对一个最基础的问题:要操作的仓库内容在哪里。

actions/checkout 就负责完成这件事。

它是一个用于检出仓库的 GitHub Action,会将仓库放入 $GITHUB_WORKSPACE,让工作流后续步骤能够访问项目文件。看起来只是自动化流程中简短的一行配置,但这一行往往是构建脚本开始读取源码、测试命令开始执行、Git 操作开始发生的起点。

项目地址:https://github.com/actions/checkout

工作流里的第一段落地动作

一个自动化流程可以拥有许多步骤。

它可以安装依赖,可以执行测试,可以生成文件,可以推送提交,也可以根据事件处理拉取请求。但在这些事情发生以前,工作流通常需要先把对应仓库检出到运行环境中。

最常见的写法很简单:

1
- uses: actions/checkout@v7

这条配置会将触发工作流的仓库检出到 $GITHUB_WORKSPACE

于是,原本只存在于远程仓库中的文件,开始出现在当前工作流可以操作的目录里。构建工具能够找到配置文件,测试命令能够读取源码,脚本也能够在项目目录中运行。

checkout 像是工作流为代码打开的一扇门。

门打开之后,后续的自动化步骤才真正拥有了可以处理的对象。

默认只获取一次触发所需的提交

actions/checkout 的默认行为并不是拉取仓库的全部历史。

默认情况下,它只会获取触发当前工作流的 ref 或 SHA 对应的单个提交。这种方式让工作流可以直接开始使用所需代码,而不必默认加载所有分支和标签的完整历史。

如果工作流确实需要完整历史,可以将 fetch-depth 设置为 0

1
2
3
- uses: actions/checkout@v7
with:
fetch-depth: 0

此时,Action 会获取所有分支与标签的完整历史。

这种选择让工作流能够根据实际任务决定获取范围。

有些流程只需要当前提交的源码,用默认配置即可开始。

有些流程需要访问历史提交、分支或标签,就可以显式要求完整历史。

代码检出不再只是单一动作,而是能够围绕工作流需求调整的基础环节。

从指定分支、标签或 SHA 开始

工作流触发时,checkout 会默认使用相应事件的 ref 或 SHA。

但自动化任务并不总是只围绕触发工作流的代码执行。有时,工作流需要明确检出某个分支、某个标签,或者一个指定的 SHA。

ref 输入项正是为这种场景准备的:

1
2
3
- uses: actions/checkout@v7
with:
ref: my-branch

当指定 ref 后,工作流可以将目标切换到所需的分支、标签或 SHA。

这让 checkout 不只服务于当前事件,也可以成为工作流中获取特定代码版本的入口。

如果希望检出 HEAD^,则需要先取得足够的提交深度:

1
2
3
4
5
- uses: actions/checkout@v7
with:
fetch-depth: 2

- run: git checkout HEAD^

先获取两个提交,再切换到前一个提交。这样一个简单的流程,也体现了检出深度与后续 Git 操作之间的联系。

只取需要的文件,让工作流更聚焦

并非每一个自动化任务都需要整个仓库。

有时,工作流只关心根目录中的文件。有时,它只需要 .githubsrc 目录。还有些场景甚至只需要一份单独的 README.md

actions/checkout 支持稀疏检出,可以通过 sparse-checkout 指定需要获取的路径。

只获取根目录文件:

1
2
3
- uses: actions/checkout@v7
with:
sparse-checkout: .

只获取 .githubsrc 目录:

1
2
3
4
5
- uses: actions/checkout@v7
with:
sparse-checkout: |
.github
src

只获取一个文件:

1
2
3
4
5
- uses: actions/checkout@v7
with:
sparse-checkout: |
README.md
sparse-checkout-cone-mode: false

对于需要进一步控制检出方式的场景,还可以使用 filter 执行部分克隆。设置 filter 后,它会覆盖稀疏检出配置。

检出动作因此不必总是大范围地搬运文件。它可以只拿到当前流程真正需要的内容,让代码获取的范围更贴近工作流目标。

多个仓库,也可以进入同一个工作流

自动化任务有时不会只处理一个仓库。

主仓库可能需要配合工具仓库、私有依赖仓库或其他项目文件共同运行。actions/checkout 支持在一次工作流中检出多个仓库,并可以通过 path 决定它们在工作区中的位置。

将多个仓库并排放置:

1
2
3
4
5
6
7
8
9
10
- name: Checkout
uses: actions/checkout@v7
with:
path: main

- name: Checkout tools repo
uses: actions/checkout@v7
with:
repository: my-org/my-tools
path: my-tools

第一个仓库被放到 main 目录,第二个仓库被放到 my-tools 目录。工作流随后便可以在同一个环境中访问两份不同的代码。

也可以让第二个仓库检出到当前仓库之下:

1
2
3
4
5
6
7
8
- name: Checkout
uses: actions/checkout@v7

- name: Checkout tools repo
uses: actions/checkout@v7
with:
repository: my-org/my-tools
path: my-tools

多仓库工作流并不意味着必须使用完全不同的方式处理代码。通过重复使用 checkout,并为每份代码设定合适的位置,多个仓库便可以共同进入同一个自动化现场。

访问私有仓库时的令牌选择

当需要检出其他私有或内部仓库时,默认的 ${{ github.token }} 只作用于当前仓库。

如果工作流需要访问另一个私有仓库,则需要提供自己的个人访问令牌。

1
2
3
4
5
6
7
8
9
10
11
- name: Checkout
uses: actions/checkout@v7
with:
path: main

- name: Checkout private tools
uses: actions/checkout@v7
with:
repository: my-org/my-private-tools
token: ${{ secrets.GH_PAT }}
path: my-tools

其中,token 可以用于获取仓库,也可以被配置到本地 Git 设置中,让后续脚本能够执行经过认证的 Git 命令。

除了个人访问令牌,checkout 也支持通过 SSH 密钥获取仓库。相关配置包括 ssh-keyssh-known-hostsssh-strictssh-user

默认情况下,SSH 用户为 git

默认情况下,严格主机密钥检查处于启用状态。

GitHub 的公开 SSH 主机密钥会被隐式加入。

这些配置让不同认证方式能够进入工作流中的检出流程,同时也让工作流能够针对远程主机与凭据进行更细致的安排。

凭据能服务于 Git,也应当被妥善收起

Git 自动化经常离不开认证。

检出私有仓库需要认证,后续的 git fetchgit push 也可能需要认证。actions/checkout 提供 persist-credentials 选项,用于决定是否将令牌或 SSH 密钥配置到本地 Git 设置中。

默认情况下,persist-credentialstrue

这意味着,工作流后续执行的 Git 命令可以继续使用经过配置的凭据。任务结束后的 post-job 清理步骤会移除相应凭据。

在 Checkout v6 中,凭据存储方式得到了调整。persist-credentials 不再将凭据直接存储在 .git/config 中,而是将凭据放在 $RUNNER_TEMP 下的单独文件中。

这个变化并不要求工作流改写原有的 git fetchgit push 等命令。后续 Git 操作仍然可以自动工作,但凭据的存储位置发生了改变。

如果不希望持久化凭据,可以明确关闭:

1
2
3
- uses: actions/checkout@v7
with:
persist-credentials: false

认证是自动化流程中不可忽略的一部分。checkout 既让认证后的 Git 命令能够继续运行,也提供了控制凭据持久化的选择。

为仓库环境做好清理与安全目录配置

每一次检出之前,工作区的状态同样值得关注。

actions/checkout 默认会在获取代码前执行以下操作:

1
git clean -ffdx && git reset --hard HEAD

这一行为由 clean 输入项控制,默认值为 true

它让检出过程能够从更明确的工作区状态开始,避免此前遗留的未跟踪文件或本地改动影响当前流程。

此外,checkout 默认会将仓库路径加入 Git 的全局 safe.directory 配置。

1
2
3
- uses: actions/checkout@v7
with:
set-safe-directory: true

对应行为是执行:

1
git config --global --add safe.directory <path>

set-safe-directory 默认值同样为 true

工作流执行并不只是把文件下载到目录中。它还涉及工作区状态、Git 配置与后续命令是否能够顺利运行。checkout 将这些基础操作整理为可配置的步骤,让检出后的环境更适合继续完成自动化任务。

Git 不可用时,仍有另一条获取文件的路径

Runner 环境中的 Git 并不总是满足版本要求。

当系统路径中不存在 Git 2.18 或更高版本时,actions/checkout 会回退到 REST API 来下载文件。

这让检出动作并不完全依赖于符合条件的本地 Git 环境。

在正常情况下,Action 使用 Git 处理仓库检出。

当 Git 版本不足时,它仍然可以通过 REST API 获得文件。

这种回退方式让工作流在不同环境中的代码获取过程拥有额外的适应空间。

拉取请求中的代码,到底检出哪一个提交

拉取请求工作流中的检出,往往需要特别明确目标。

如果希望检出拉取请求的 HEAD 提交,而不是合并提交,可以将 ref 设置为拉取请求头部提交的 SHA:

1
2
3
- uses: actions/checkout@v7
with:
ref: ${{ github.event.pull_request.head.sha }}

如果工作流由拉取请求事件触发,并需要向对应分支推送提交,则需要显式指定 ref。这是因为 GitHub Actions 在拉取请求触发场景中会以 detached HEAD 模式检出代码,并不会默认检出分支。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
on: pull_request

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
ref: ${{ github.head_ref }}
- run: |
date > generated.txt
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add .
git commit -m "generated"
git push

如果只是处理拉取请求关闭事件,也可以在触发器中包含 closed 类型:

1
2
3
4
5
6
7
8
9
10
on:
pull_request:
branches: [main]
types: [opened, synchronize, closed]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

不同事件拥有不同上下文,检出动作也需要围绕实际目标做出安排。checkout 为这些工作流场景提供了对应的配置入口。

对来自 Fork 的拉取请求保持谨慎

Checkout v7 对来自 Fork 的拉取请求处理增加了更严格的默认安全行为。

当工作流由 pull_request_targetworkflow_run 触发时,checkout 默认拒绝检出 Fork 拉取请求中的代码。

这类触发器会使用基础仓库的 GITHUB_TOKEN、机密信息、默认分支缓存范围与 Runner 访问权限。若在这样的受信任上下文中获取并执行来自 Fork 的代码,可能引发所谓的 pwn request 漏洞。

如果经过风险审查后,确实需要检出 Fork 拉取请求代码,可以显式设置:

1
2
3
- uses: actions/checkout@v7
with:
allow-unsafe-pr-checkout: true

这一选项默认值为 false

它的存在提醒着自动化流程中的一个重要事实:检出代码不是完全中立的文件操作。被检出的代码可能会进入后续命令、构建与脚本执行路径,因此来源、权限与触发方式都需要被认真对待。

从检出到提交,让自动化写回仓库

checkout 不只可以让工作流读取代码,也可以为后续提交与推送提供基础。

使用内置令牌推送一个自动生成的提交,可以这样配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
on: push

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- run: |
date > generated.txt
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add .
git commit -m "generated"
git push

在这个流程中,checkout 先将代码带入工作区。

随后,脚本创建 generated.txt

接着,Git 配置提交身份,暂存文件,创建提交并推送。

这条链路很清楚地展示了 checkout 的位置。它不是提交动作本身,却是后续 Git 操作能够成立的重要前提。没有可操作的工作区,就没有基于当前仓库状态继续生成、提交与推送的过程。

权限设置中的一个清晰起点

使用 checkout 时,README 推荐为 GITHUB_TOKEN 设置以下权限:

1
2
permissions:
contents: read

这一权限配置为读取仓库内容提供了明确的基础。

当工作流存在其他认证方式或其他任务要求时,权限可以根据实际情况调整。但对于检出仓库这一基础动作而言,contents: read 是一个直接而清晰的起点。

权限越清晰,工作流的职责也越容易被理解。

读取代码,就给予读取代码所需的权限。

需要额外能力时,再围绕具体工作流目标进行配置。

从 v4 到 v7,持续调整安全与运行基础

actions/checkout 的版本演进中,可以看到它对运行时、安全与依赖的持续调整。

Checkout v5 更新到 Node 24 运行时,并要求最低 Actions Runner 版本为 v2.327.1。

Checkout v6 调整了 persist-credentials 的凭据存储位置,让认证信息不再直接写入 .git/config,而是存放在 $RUNNER_TEMP 下的独立文件中。

Checkout v7 则迁移到 ESM,以支持更新版本的 @actions 软件包。同时,它针对 Fork 拉取请求的受信任触发器场景加入了更安全的默认行为,并更新了直接与间接依赖,其中包含针对已知漏洞的安全修复。

这些变化让 checkout 不只是一个长期不变的检出命令。

它持续跟随 GitHub Actions 的运行环境、依赖体系与安全要求演进。对于工作流作者而言,版本升级不仅意味着写法上的变化,也意味着运行时与安全边界会继续变得更明确。

结语

自动化工作流常常从一行 checkout 开始。

这一行看起来很轻,却承接着后续所有与代码有关的动作。它让仓库进入 $GITHUB_WORKSPACE,让脚本能够读取项目文件,让构建与测试有了执行对象,也让后续的 Git 操作拥有了可以继续前进的起点。

从默认检出单个提交,到获取完整历史。

从稀疏检出少量路径,到在同一工作区中处理多个仓库。

从令牌与 SSH 密钥,到凭据持久化与清理。

从拉取请求的 HEAD 提交,到 Fork 代码的安全边界。

actions/checkout 将这些围绕代码获取而展开的细节,收拢为 GitHub Actions 工作流中的一个基础环节。

代码抵达工作区之后,自动化才真正开始。