actions/checkout是 GitHub Actions 中最常用的 Action 之一,几乎所有 CI 工作流的第一步都是从它开始的。但对于刚接触 GitHub Actions 的开发者来说,checkout 到底做了什么、有哪些参数需要关注、为什么有时候拉下来的代码不完整、子模块要怎么处理,这些问题往往散落在各个 issue 和博客里,不成体系。本文会从核心概念讲起,结合完整示例、参数说明和常见报错,带你彻底搞懂 GitHub Actions 中 checkout 的用法和原理,并与 Git 本地的git checkout命令做区分,避免混淆。
1. 背景与核心概念
1.1 什么是 actions/checkout
actions/checkout是 GitHub 官方提供的一个 Action,它的作用是在 GitHub Actions 的运行环境中拉取当前仓库的代码,让后续的 CI 步骤能够基于这份代码执行构建、测试、打包等操作。
可以这样理解:GitHub Actions 的工作流运行在一个独立的虚拟机上,这个环境一开始是空白的、和我们本地开发环境无关的。即使工作流是因为某个仓库的 push 事件触发的,运行环境本身也不会自动携带仓库代码。我们需要显式地执行 checkout 动作,才能把代码“取”到 runner 中。
因此,几乎每个 GitHub Actions 工作流的第一段都是:
steps: - name: 拉取代码 uses: actions/checkout@v4这段配置的含义是:让当前 job 使用actions/checkout@v4这个 Action,把仓库代码检出到 runner 的工作目录中。
1.2 checkout 解决了什么问题
GitHub Actions 的定位是自动化任务执行平台。它需要的是可重复、可控、干净的执行环境。如果系统默认把仓库代码自动挂载到每个环境里,反而会带来几个问题:
- 不是所有工作流都需要仓库代码,自动检出会浪费时间和存储。
- 不同工作流可能需要在不同分支、不同 commit 上执行,自动检出无法灵活指定。
- runner 环境的镜像需要保持通用性,内置所有仓库代码不现实。
actions/checkout把“是否拉取代码”“拉取哪个分支”“拉取多深的历史”这些选择权交给了工作流编写者。这种显式声明的方式更符合 CI/CD 的工程化思路,也让工作流的行为更可预测。
1.3 checkout 和 git clone 的区别
很多人会问:checkout 不就是 git clone 吗?两者确实都是拉取代码,但存在几个明显差异:
| 对比项 | git clone | actions/checkout |
|---|---|---|
| 操作主体 | 在本地终端手动执行 | 在 GitHub Actions runner 中自动执行 |
| 身份认证 | 依赖本地 SSH key 或凭据 | 自动使用 GitHub 提供的 GITHUB_TOKEN |
| 参数能力 | 需要自己组合多个参数 | 提供封装好的 Action 参数 |
| 分支切换 | clone 后需要手动 checkout | 可直接指定要检出的 ref |
| 历史深度 | 默认为完整历史 | 可通过 fetch-depth 控制 |
| 子模块处理 | 需要手动执行相关命令 | 可通过 submodules 参数自动处理 |
actions/checkout本质上是在 runner 中执行类似 git clone 的操作,但它针对 CI 场景做了很多封装,比如自动处理认证、优化检出效率、支持稀疏检出、支持 GitHub 托管 runner 的特殊环境变量等。
1.4 常见使用场景
actions/checkout在以下场景中使用频率较高:
- 每次 push 代码后自动执行测试。
- Pull Request 创建或同步时运行静态检查。
- 发布版本时拉取指定 tag 的代码进行构建。
- 多仓库协作时,在主仓库工作流中拉取其他仓库代码。
- 需要同时拉取多个子模块的文档站或 monorepo 项目。
2. 环境准备与版本说明
2.1 GitHub Actions 基础环境
在使用actions/checkout之前,需要确保你已经具备以下条件:
- 一个 GitHub 账号和仓库。
- 仓库中已启用 GitHub Actions(默认开启)。
- 本地有 Git 基础使用经验。
- 了解 YAML 的基本语法。
GitHub 提供了两类 runner:
- GitHub 托管的 runner,例如
ubuntu-latest、windows-latest、macos-latest。 - 自托管 runner,即自己搭建的运行环境。
actions/checkout在这两类 runner 上都可以使用,但配置细节略有差异。本文示例以 GitHub 托管的ubuntu-latest为例,这部分配置对大多数项目都适用。
2.2 actions/checkout 版本说明
actions/checkout目前已经历了多个大版本,使用最广的版本是@v3和@v4。
| 版本 | 主要变化 |
|---|---|
| v2 | 基于 Node.js 运行,成为主流版本 |
| v3 | 适配 Node 16,继续兼容大部分项目 |
| v4 | 适配 Node 20,参数模型更清晰,安全性和性能有改进 |
在实际项目中,建议使用@v4或保持与项目约定一致的 tag。这里的 tag 是大版本号,GitHub Actions 会自动解析到该大版本的最新 release,不需要锁死小版本。
如果团队对可复现性要求较高,也可以锁定为具体的 release tag,例如类似actions/checkout@v4.1.1的写法。但需要特别注意的是,Actions 的 release 版本会持续更新,锁定小版本后应及时关注官方更新,避免错过安全修复。
2.3 示例项目结构
本文后续的实战案例会基于一个简单的 Node.js 项目,目录结构如下:
github-actions-checkout-demo/ ├── .github │ └── workflows │ └── ci.yml ├── package.json ├── index.js └── README.md其中ci.yml是关键,它定义了整个 GitHub Actions 工作流。其他文件是一个最小可运行的 Node.js 项目,方便我们验证 checkout 的效果。
3. checkout 操作原理拆解
3.1 checkout 的核心逻辑
actions/checkout在 runner 中做了以下几件核心事情:
- 根据
repository参数确定要拉取的仓库地址。 - 根据
ref参数确定要检出的分支、tag 或 commit SHA。 - 配置 Git 凭据,使用
GITHUB_TOKEN或自定义 token 完成身份认证。 - 克隆代码到
path参数指定的目录。 - 根据需要处理子模块、LFS 文件、稀疏检出等扩展选项。
- 切换到目标 ref,确保当前工作区与指定提交一致。
从底层看,checkout 的过程和我们在终端手动执行的 Git 命令很相似,但它在 runner 环境中自动完成了认证和环境准备。
3.2 默认参数说明
actions/checkout提供了大量参数。下面我们先看几个最重要的:
3.2.1 repository
指定要拉取的仓库,默认值是当前仓库。
- uses: actions/checkout@v4 with: repository: your-github-name/repo-name当工作流在仓库 A 中,却需要拉取仓库 B 的代码时,可以通过这个参数切换。
3.2.2 ref
指定要检出的分支、tag 或 commit SHA。默认情况下,GitHub Actions 会根据触发事件自动判断。例如 push 事件会检出对应分支的最新提交,pull_request 事件会检出 PR 的合并结果。
也可以手动指定:
- uses: actions/checkout@v4 with: ref: main或:
- uses: actions/checkout@v4 with: ref: v1.2.03.2.3 fetch-depth
控制 Git 历史的获取深度。默认值是 1,也就是只拉取最新的一个提交。这在大多数 CI 场景中已经够用,能显著加快 checkout 速度。
如果需要完整历史,可以设置为0:
- uses: actions/checkout@v4 with: fetch-depth: 0如果需要最近几个提交,比如做代码扫描或版本计算,可以设置为具体数字,例如fetch-depth: 10。
3.2.4 path
指定代码检出到 runner 的哪个目录,默认是当前工作目录$GITHUB_WORKSPACE。
- uses: actions/checkout@v4 with: path: my-code设置后,后续步骤需要进入my-code目录才能操作代码。
3.2.5 token
指定用于认证的 GitHub token,默认是${{ github.token }},也就是 GITHUB_TOKEN。这个 token 由 GitHub Actions 自动生成,权限由工作流的permissions配置决定。
如果需要拉取私有仓库或在当前仓库中 push 代码,可能需要自定义 token:
- uses: actions/checkout@v4 with: token: ${{ secrets.MY_PAT }}3.2.6 submodules
控制子模块的拉取方式。默认值为false,即不拉取子模块。如果项目使用了 submodule,需要设置为true或recursive:
- uses: actions/checkout@v4 with: submodules: recursiverecursive会递归拉取所有嵌套子模块。
3.2.7 lfs
控制是否拉取 Git LFS 大文件。默认是false:
- uses: actions/checkout@v4 with: lfs: true3.2.8 sparse-checkout
用于稀疏检出,只在需要部分目录时使用。这个参数在大型 monorepo 项目中很实用。
- uses: actions/checkout@v4 with: sparse-checkout: | src tests3.3 常见误区
3.3.1 误认为 checkout 会自动切换分支
actions/checkout会切换到目标 ref,但它不受本地git checkout命令一样的“分支切换”语义影响。在 Actions 环境中,检出的是某个具体的提交状态,后续步骤中直接运行git branch可能看到的是 detached HEAD 状态。这通常是正常的,因为 CI 只需要代码内容,并不关心你当前在哪个分支上工作。
3.3.2 忽略 fetch-depth 对版本号计算的影响
很多项目在 CI 中根据 Git tag 计算版本号。此时如果fetch-depth为 1,可能无法取得最近的 tag,导致版本号错误。这种情况下需要将fetch-depth设置为0。
3.3.3 把 actions/checkout 和 git checkout 混为一谈
这个点非常常见。Git 命令git checkout用于本地分支切换或文件恢复,而actions/checkout是 GitHub Actions 的一个 Action,两者解决的问题完全不同。在写工作流时,使用的是 GitHub Actions 的语法;在写 shell 命令时,才会用到git checkout。理解这个区别对排查问题很有帮助。
4. 完整实战案例
下面我们从头开始构建一个 GitHub Actions 工作流,逐步演示actions/checkout的使用方法。
4.1 创建项目结构
先在 GitHub 上创建一个新仓库,然后在本地把项目克隆下来。仓库名称可以命名为github-actions-checkout-demo。
在本地创建以下文件:
package.json:
{ "name": "github-actions-checkout-demo", "version": "1.0.0", "description": "A simple demo for actions/checkout", "main": "index.js", "scripts": { "test": "node index.js" } }index.js:
console.log('Hello from GitHub Actions checkout demo!');README.md:写入简单的项目说明。
4.2 编写第一个工作流
在项目根目录创建.github/workflows/ci.yml,内容如下:
name: CI on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: runs-on: ubuntu-latest steps: - name: 拉取代码 uses: actions/checkout@v4 - name: 查看检出后的文件 run: | pwd ls -la - name: 运行测试 run: npm test将这个文件提交并推送到 GitHub 后,打开仓库的 Actions 页面,可以看到工作流开始执行。
4.3 运行与验证
工作流执行时,你会看到拉取代码这一步使用了actions/checkout@v4。这一步完成后,后续步骤就能访问到仓库中的代码。
查看检出后的文件步骤会输出当前工作目录和文件列表,结果类似:
/home/runner/work/github-actions-checkout-demo/github-actions-checkout-demo total 16 drwxr-xr-x 3 runner docker 4096 ... drwxr-xr-x 3 runner docker 4096 ... -rw-r--r-- 1 runner docker 76 ... README.md drwxr-xr-x 2 runner docker 4096 ... index.js drwxr-xr-x 2 runner docker 4096 ... package.json这说明 checkout 成功地把仓库代码放到了 runner 的工作目录中。
4.4 使用参数控制检出行为
下面我们扩展工作流,加入更丰富的 checkout 参数。
4.4.1 拉取指定分支
steps: - name: 拉取 main 分支 uses: actions/checkout@v4 with: ref: main这种方式适合手动触发的 workflow_dispatch 场景,或者需要固定分支的发布流程。
4.4.2 拉取完整历史
steps: - name: 拉取完整 Git 历史 uses: actions/checkout@v4 with: fetch-depth: 0在需要根据 Git tag 生成版本号、统计提交数量、执行某些需要历史信息的扫描工具时,需要这样配置。
4.4.3 检出到指定目录
steps: - name: 拉取代码到 build 目录 uses: actions/checkout@v4 with: path: build - name: 进入目录查看文件 run: ls -la build当工作流需要同时检出多个仓库代码时,这个参数特别有用。例如:
steps: - name: 拉取主仓库代码 uses: actions/checkout@v4 with: path: main-repo - name: 拉取工具仓库代码 uses: actions/checkout@v4 with: repository: your-name/tool-repo path: tool-repo4.4.4 处理子模块
如果项目使用 submodule,需要在 checkout 时指定:
steps: - name: 拉取代码并初始化子模块 uses: actions/checkout@v4 with: submodules: recursive配置了submodules: recursive后,checkout 会自动运行git submodule sync --recursive和git submodule update --init --recursive,不需要再手动执行额外的 shell 命令。
4.5 完整的多场景工作流参考
下面是一个相对完整的示例,展示了多个参数的组合使用:
name: Full Checkout Demo on: workflow_dispatch: jobs: demo: runs-on: ubuntu-latest steps: - name: 检出主仓库代码(完整历史) uses: actions/checkout@v4 with: repository: your-name/github-actions-checkout-demo ref: main fetch-depth: 0 path: app - name: 显示检出目录 run: ls -la app - name: 显示当前 Git 提交信息 run: git -C app log --oneline -5这个工作流可以通过仓库 Actions 页面的Run workflow按钮手动触发。它演示了如何指定仓库、分支、获取完整历史,以及将代码检出到自定义目录。
5. 常见问题与排查思路
5.1 常见报错一览
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
Repository not found | token 没有权限访问仓库 | 确认使用正确的 token,检查仓库名称和权限 |
fatal: unable to access ... | 网络问题或认证失败 | 检查网络,确认 token 有效,自托管 runner 检查代理配置 |
| 检出后模块找不到 | 子模块未初始化 | 设置submodules: recursive |
| Git tag 无法获取 | fetch-depth 为 1 | 设置fetch-depth: 0 |
| 代码不在期望目录 | path 参数设置影响 | 检查 path 参数,后续步骤切换到对应目录 |
Permission denied | GITHUB_TOKEN 权限不足 | 调整工作流 permissions 或使用自定义 token |
| checkout 卡住或超时 | 仓库过大或网络慢 | 使用稀疏检出、设置更大的 timeout,或检查 runner 网络 |
5.2 拉取私有仓库失败
如果当前工作流需要拉取另一个私有仓库的代码,甚至还需要在当前仓库中使用私有 Action,那 checkout 时依赖的默认 GITHUB_TOKEN 可能权限不够。
常见做法是创建一个 Personal Access Token,在仓库的 Secrets 中保存,然后在 checkout 时引用:
- uses: actions/checkout@v4 with: repository: your-name/private-repo token: ${{ secrets.MY_PAT }}需要特别注意的是,个人访问令牌的权限比较大,建议使用 GitHub App 的 token 或GITHUB_TOKEN配合精细化的permissions配置,避免长期使用权限较大的个人令牌。
5.3 checkout 的 ref 参数并非总是生效
ref参数在大多数情况下都能正确指定分支、tag 或 commit,但如果你同时指定了repository参数,并且repository指向的是当前仓库,行为与默认一致。如果指向其他仓库,需要确认目标仓库中确实存在这个 ref。
另外在pull_request事件中,默认 checkout 的是 PR 合并后的提交,而不是 PR 源分支的最新提交。这是 Actions 的设计行为,目的是模拟合并后的状态。如果你需要 checkout 源分支,可以依赖github上下文中的参数自行指定:
- uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }}但要注意,这种情况下需要确认 token 有权限访问源分支所在的仓库。
5.4 子模块拉取失败
子模块拉取失败的常见原因有两个:
- 子模块是私有仓库,checkout 时使用的 token 没有权限。
- 子模块内容较大,默认的 fetch 策略超时。
解决方案是在 checkout 时传入有权限的 token:
- uses: actions/checkout@v4 with: submodules: recursive token: ${{ secrets.MY_PAT }}如果子模块中还嵌套了子模块,需要使用recursive而不是true。
5.5 LFS 文件未拉取
如果仓库使用了 Git LFS,并且 CI 构建需要这些大文件,需要在 checkout 时开启 LFS:
- uses: actions/checkout@v4 with: lfs: true注意,开启 LFS 会拉取大量数据,可能增加整体构建时间。如果构建不需要 LFS 文件,保持默认关闭即可,这样可以加快 checkout 速度。
5.6 自托管 runner 的注意事项
在自托管 runner 上使用actions/checkout时,需要确保 runner 环境中已经安装 Git,并且 Git 版本足够新。部分旧版 Git 可能无法正确处理某些 GitHub 的新特性。
如果 runner 所在环境需要代理访问外部网络,还需要检查代理配置是否会影响 checkout 过程中的 HTTPS 请求。
6. 最佳实践与工程建议
6.1 尽量使用默认参数
对于大多数项目,默认的actions/checkout@v4就能满足需求。默认fetch-depth: 1能显著减少 checkout 耗时,而且大多数构建和测试命令并不需要完整历史。不要一开始就把fetch-depth设置为0,等确实需要完整历史时再调整。
6.2 重视 token 权限最小化
actions/checkout默认使用GITHUB_TOKEN,而这个 token 的权限范围由工作流顶部的permissions决定。建议在工作流中显式声明权限,而不是依赖仓库全局设置。例如:
permissions: contents: read如果 checkout 后还需要在当前仓库中提交代码,例如自动生成文档并推送,则需要声明contents: write。此时要格外小心,避免工作流在每次 push 时再次触发自身,形成循环。
6.3 在需要时使用稀疏检出
如果你的仓库很大,而 CI 只需要其中部分目录,可以考虑使用sparse-checkout。这个功能在 monorepo 架构下能大幅减少检出耗时。
- uses: actions/checkout@v4 with: sparse-checkout: | packages/app packages/shared需要注意的是,稀疏检出后git status、git diff等操作可能只反映已检出的文件,某些工具可能因此表现异常。
6.4 根据事件类型合理选择 ref
在写工作流时,默认 checkout 逻辑已经能覆盖绝大多数场景。但如果你的工作流需要更复杂的引用关系,建议先理解github上下文中的几个关键字段:
| 字段 | 含义 |
|---|---|
github.sha | 触发工作流的 commit SHA |
github.ref | 触发工作流的分支或 tag |
github.event.pull_request.head.sha | PR 源分支的最新 commit |
github.event.pull_request.base.sha | PR 目标分支的最新 commit |
合理使用这些字段,可以让 checkout 更精确地定位到需要分析的提交。
6.5 避免在 checkout 后修改 Git 配置导致问题
有些项目会在工作流中手动执行git config user.name或git config user.email。这些操作在本地没有问题,但在 CI runner 中,如果后续有自动提交需求,需要正确设置这些值。建议通过环境变量或专门的步骤来管理,不要在 checkout 之前强行修改全局 Git 配置,以免影响其他步骤。
6.6 缓存与 checkout 的顺序
在 GitHub Actions 中,缓存恢复和 checkout 的顺序会影响效率。如果用了actions/cache恢复依赖缓存,并且缓存内容不依赖代码内容,通常可以先恢复缓存再 checkout。如果缓存 key 中包含 commit SHA,那么必须先 checkout,才能知道当前的 SHA,然后构造缓存 key。
这里给出一个常见的组合示例:
steps: - name: 拉取代码 uses: actions/checkout@v4 - name: 恢复 npm 缓存 uses: actions/cache@v4 with: path: ~/.npm key: npm-${{ hashFiles('package-lock.json') }}6.7 关注 checkout 和 git checkout 的语义区别
回到文章开始时提到的混淆点,actions/checkout解决的是“在 CI 环境中检出代码”的问题,git checkout解决的是“在本地仓库中切换分支或恢复文件”的问题。如果在一个 workflow 的run步骤里写 shell 命令,需要操作 Git 时,依然可以使用git checkout,但在uses关键字下,我们使用的是 GitHub Actions 的 Action 语法。
这种区分在排查问题时非常重要。例如在 CI 日志中看到Run actions/checkout@v4,这是 Action 的输出;看到git checkout main,才是 shell 命令的输出。理解了这一层,就不会因为报错信息来自不同的层而困惑。
6.8 定期关注 Action 版本更新
GitHub 官方会不定期发布actions/checkout的新版本,修复安全漏洞或兼容性问题。建议在 CI 中定期检查使用的版本,尤其是在 GitHub 官方通知 Node.js 版本不再受支持时,及时升级到新的大版本。
升级时可以先在一个不关键的工作流中试用,确认无误后再推广到所有仓库。不要同时调整大量配置,避免问题出现时难以定位。
7. 总结与学习路线
本文从 GitHub Actions 中的actions/checkout入手,介绍了它的核心概念、与git clone的区别、常用参数、完整实战案例以及典型报错的排查方法。同时,我们重点区分了actions/checkout和 Git 本地命令git checkout的语义差异,这个区分在实际排查中非常关键。
如果你刚开始接触 GitHub Actions,建议按以下顺序继续深入:
- 先掌握
on事件触发、jobs、steps的基础写法。 - 理解
github上下文中的常用字段。 - 尝试在一个简单项目中加入测试、构建步骤。
- 掌握
actions/upload-artifact和actions/download-artifact,了解如何传递构建产物。 - 深入学习
actions/cache,优化依赖安装速度。 - 最后再研究仓库权限、环境变量、自托管 runner 等高阶内容。
actions/checkout虽然只是 CI 流程中的第一步,但很多构建问题其实都出在这一步。把它的参数和原理搞清楚,后续的 CI 优化会顺利很多。建议你在自己的项目中多写几个小实验,分别试试不同的ref、fetch-depth、path和submodules组合,观察实际行为,这样才能真正掌握它。如果本文对你有帮助,可以收藏备用,后面用到时候直接翻出来照做。