尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

深入解析GitHub Actions中的actions/checkout:原理、参数与排错指南

深入解析GitHub Actions中的actions/checkout:原理、参数与排错指南 在实际的 GitHub Actions 工作流里几乎没有一个项目能绕开actions/checkout。它是 GitHub 官方提供的 action职责是在 runner 上把仓库代码拉取到工作目录让后续的安装依赖、执行测试、构建镜像等步骤有代码可用。不过很多刚开始写 workflow 的开发者会把actions/checkout和 Git 命令git checkout混在一起以为在 workflow 里写一个run: git checkout ...就能“检出仓库”结果发现工作区里根本没有代码或者检出的分支不符合预期。这里需要先分清两件事actions/checkout是 GitHub Actions 中的一个动作action它解决的是“自动化任务从哪里拿到代码”的问题git checkout是 Git 的一个子命令它解决的是“在当前仓库里切换到哪个分支或提交”的问题。二者名字接近但所在层次和使用方式完全不同。这篇文章围绕actions/checkout展开讲清楚它的工作原理、核心参数、典型用法、常见报错和排查方式并且会专门讨论什么时候应该用actions/checkout的ref参数什么时候需要手动执行git checkout希望帮助你在写 CI 流程时少走弯路。1. actions/checkout 为什么是 GitHub Actions 的第一步1.1 一次 CI 运行里的代码来源一个 workflow job 启动后runner 会先分配一个工作目录这个目录通常以$GITHUB_WORKSPACE表示。在默认情况下runner 不会自动把代码放进去。也就是说如果你的 workflow 没有使用actions/checkout后续步骤里执行ls看到的是一个近乎空的工作目录npm ci、mvn package、docker build都会因为没有源码而报错。actions/checkout的职责就是把指定仓库、指定 ref 的代码下载到这个工作目录。它拿到代码之后后续步骤才能基于源码继续操作。1.2 actions/checkout 和 git checkout 的区别虽然名称里都有 “checkout”两者的作用对象和使用层级完全不同对比项actions/checkoutgit checkout使用层级GitHub Actions 中的 step 动作Git 命令行子命令主要功能将远程仓库克隆/检出到 runner 工作目录切换当前仓库的工作区到指定分支、标签或提交是否自动处理认证是默认使用GITHUB_TOKEN需要用户在运行环境中提前配置好凭据是否自动解析触发事件是可以根据 push、pull_request 等事件自动选择 ref否必须手动指定分支或提交典型写法uses: actions/checkoutv4run: git checkout main使用场景workflow 一开始获取仓库代码在已有代码中切换分支、标签或回退提交在 GitHub Actions 中不能把uses: actions/checkoutv4简单替换成run: git checkout。因为后者假设仓库已经存在于当前目录中而实际上 runner 刚开始并没有仓库。不过当actions/checkout完成之后后续步骤确实是在一个 Git 仓库里工作所以确实可以在需要切换分支时手动执行git checkout。这是很多开发者产生混淆的根源不是同一层操作却在同一个 job 里先后出现。1.3 actions/checkout 到底做了什么actions/checkout不是简单地执行一条git clone它内部会按顺序处理多件事确定目标仓库默认是当前 workflow 所在的仓库也可以通过repository参数指定其他仓库。确定要检出的 ref默认是触发 workflow 的事件对应的 ref。使用 GITHUB_TOKEN 或自定义 token 配置远端地址避免 Git 在拉取过程中出现交互式输入密码。执行等效于git fetch的操作把目标 ref 对应的提交拉取到 runner 本地。在目标 ref 上创建 detached HEAD或切到对应分支。默认执行清理操作删除工作区里的残留文件。根据submodules、lfs等参数决定是否同步子模块或 Git LFS 对象。其中 detached HEAD 这一点经常被忽略。actions/checkout检出的提交通常不是处于一个普通分支上而是 detached HEAD 状态。这对后续要执行git push或基于分支名操作的流程有影响需要在用到时主动处理。注意actions/checkout检出后通常处于 detached HEAD 状态。如果后续步骤需要执行git push要确保 push 的 ref 正确必要时要先创建或切换分支。2. 先跑通最小示例把代码检出再验证2.1 准备一个 workflow 文件在仓库中创建.github/workflows/checkout-demo.yml。这个文件本身也是仓库的一部分GitHub 会自动识别并执行里面的 workflow。2.2 一个最基础的检出示例name: checkout-demo on: push: branches: - main jobs: show-code: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkoutv4 - name: Show workspace content run: | pwd ls -la git log --oneline -1这里的uses: actions/checkoutv4会在main分支收到 push 后把最新提交的代码检出到 runner 工作目录。第二个 step 中的pwd会打印当前工作目录ls -la会显示仓库根目录内容git log --oneline -1会显示当前检出的提交。2.3 检出后目录里有什么在 GitHub 托管的 runner 上工作目录默认是/home/runner/work/{仓库名}/{仓库名}。检出完成后目录里会有.git目录、源码文件、README、.github目录等。由于默认使用浅克隆.git目录会比较小只保留最新一次提交和相关对象。如果这个 job 里没有actions/checkout那么后续的git log会直接报错因为当前目录根本不是 Git 仓库。这也是判断是否成功检出的最简单方式。2.4 如果不加 actions/checkout 会看到什么jobs: no-checkout: runs-on: ubuntu-latest steps: - name: List current directory run: ls -la这个 job 没有使用actions/checkout运行时当前目录里只有 runner 自己生成的极少文件不会有仓库代码。实际开发中很多新手遇到的 “找不到 package.json”“找不到 pom.xml” 正是这个原因。3. 核心参数详解按场景控制检出行为3.1 参数总览actions/checkout的常用参数如下表所示。不同版本之间参数会有差异落地前先看对应版本 README。参数默认值作用repository当前仓库指定要检出的仓库支持owner/repo格式ref触发工作流的事件 ref指定要检出的分支、标签或提交 SHAtokenGITHUB_TOKEN用于访问仓库的认证令牌fetch-depth1拉取的提交深度0表示拉取全部历史persist-credentialstrue是否把 token 持久化到 git configpath$GITHUB_WORKSPACE检出到工作目录下的哪个子目录cleantrue检出前是否清理工作区残留文件submodulesfalse是否一并检出子模块可设置为recursivelfsfalse是否下载 Git LFS 文件sparse-checkout不启用只检出仓库的部分目录set-safe-directorytrue是否配置 Git safe.directory解决 owner 不一致问题3.2 fetch-depth浅克隆和完整历史fetch-depth是使用频率最高的参数之一。默认值是1也就是只拉取最新一次提交。这种浅克隆在大多数构建任务里已经足够因为 CI 通常只需要最新代码。但是当后续步骤需要做这些事时默认值就不够了执行git diff HEAD^ HEAD比较提交历史。根据git describe生成版本号。分析 PR 中所有变更文件。统计两个版本之间的提交数量。这时可以设置为fetch-depth: 0拉取全部历史或者设置为一个足够大的数字。也需要注意拉取全部历史在大型仓库里会明显增加耗时和磁盘占用不能无脑使用。- uses: actions/checkoutv4 with: fetch-depth: 03.3 ref检出的分支、标签、SHA 如何选择ref参数决定最终检出哪个提交。如果你不填默认由触发事件决定push事件检出被推送的分支。pull_request事件检出 PR 的合并提交而不是源分支的最新提交。workflow_dispatch事件默认检出默认分支。schedule事件默认检出默认分支。这里很容易踩坑。比如在 PR 检查工作流中你希望检出 PR 源分支上的最后提交但默认行为是检出源分支与目标分支合并后的提交。如果后续要基于 PR 源码做独立分析需要显式指定- uses: actions/checkoutv4 with: ref: ${{ github.head_ref }}如果要检出某个标签- uses: actions/checkoutv4 with: ref: refs/tags/v1.0.0如果要检出某个具体提交- uses: actions/checkoutv4 with: ref: ${{ github.sha }}github.sha是触发工作流的提交 SHA在workflow_dispatch或push场景下很有用。3.4 token 和 persist-credentials认证与后续 git push 权限actions/checkout默认使用GITHUB_TOKEN。GITHUB_TOKEN是 GitHub Actions 自动生成的临时令牌作用范围通常限定在当前仓库。它是否具有写权限取决于仓库 Settings 里的 Workflow permissions 配置。对于当前仓库的普通读写操作默认令牌一般够用。但是默认令牌无法访问其他私有仓库。如果你需要checkout另一个私有仓库必须通过token参数传入一个有权限的 personal access tokenPAT或其他 GitHub App token。- uses: actions/checkoutv4 with: repository: owner/private-repo token: ${{ secrets.PRIVATE_REPO_TOKEN }}persist-credentials默认是true表示会把 token 写入 Git 配置使后续步骤中的git push、git submodule update等命令继续使用同一个凭据。如果 job 中明确不会有写回仓库的操作可以设置persist-credentials: false减少凭据在 runner 上的暴露时间。3.5 submodules 和 LFS包含外部引用如果仓库包含 Git 子模块只写一个普通actions/checkout不会拉取子模块内容。你需要设置- uses: actions/checkoutv4 with: submodules: recursiverecursive表示嵌套子模块也一并处理。如果子模块本身是私有仓库还需要额外传入有读取权限的 token例如- uses: actions/checkoutv4 with: submodules: recursive token: ${{ secrets.SUBMODULE_TOKEN }}如果仓库使用 Git LFS 管理大文件需要设置lfs: true。一个常见问题是在浅克隆状态下 LFS 文件可能只检出指针文件而不是真实内容。遇到这种问题时可以尝试将fetch-depth: 0与lfs: true同时使用- uses: actions/checkoutv4 with: fetch-depth: 0 lfs: true3.6 path、clean 和 sparse-checkout控制代码位置与清理策略path参数可以指定检出到工作目录下的子目录。当你需要在一个 job 中检出多个仓库时这个参数很有用- uses: actions/checkoutv4 with: path: frontend - uses: actions/checkoutv4 with: repository: owner/backend path: backend使用path之后要注意后续步骤的默认工作目录仍然是整个 job 的工作区根目录而不是你指定的子目录。需要执行命令时要么使用working-directory要么先cd进对应目录。clean默认是true会在检出前删除工作区中的未跟踪文件这适合自托管 runner 复用同一目录的场景。如果希望利用上一次构建留下的文件做增量构建可以考虑设为false但要额外处理残留文件带来的不确定性。sparse-checkout适用于 monorepo 中只需要某个子目录的场景。例如只想检出apps/admin- uses: actions/checkoutv4 with: sparse-checkout: apps/admin这样可以减少下载内容但需要确保你的构建逻辑不依赖仓库其他目录。注意设置sparse-checkout后Git 工作区默认只包含指定目录。如果后续命令需要访问其他路径会提示文件不存在。4. 实际项目中的组合用法4.1 先拉代码再判断变更范围很多项目会先checkout然后根据变更内容决定是否继续构建。例如前端 monorepo 中只有前端目录发生变化时才执行构建。示例- uses: actions/checkoutv4 with: fetch-depth: 0 - name: Check frontend changes id: check run: | git diff --name-only HEAD~1 HEAD | grep -E ^(frontend/|package.json|yarn.lock) || true这里的git diff需要足够的历史记录所以fetch-depth不能是默认的1。如果只取最新提交HEAD~1会报错。这也是“先想清楚后续命令需要什么再选fetch-depth”的典型例子。4.2 多仓库检出在集成测试场景中经常要同时检出应用代码和测试配置仓库。前面已经介绍过用两次actions/checkout配合repository和path即可- uses: actions/checkoutv4 with: path: app - uses: actions/checkoutv4 with: repository: owner/ci-config token: ${{ secrets.CI_CONFIG_TOKEN }} path: ci-config随后在构建步骤中使用working-directory指定在哪个目录中执行命令。需要注意两次检出的仓库彼此独立不要在子目录里假设另一个仓库已经存在。4.3 配合缓存和制品上传actions/checkout通常放在 workflow 开头后面紧跟着actions/cache和构建命令。顺序不要乱steps: - uses: actions/checkoutv4 - uses: actions/cachev4 with: path: ~/.npm key: npm-${{ runner.os }}-${{ hashFiles(package-lock.json) }} - run: npm ci这里checkout先把package-lock.json拉下来actions/cache才能计算缓存 key。如果顺序反了缓存步骤拿不到锁文件key 会不稳定。4.4 自托管 runner 上的差异在 GitHub 托管 runner 上每次 job 都是全新环境所以clean: true的影响不大。但在自托管 runner 上同一个工作目录会被多个 job 复用可能出现上次构建生成的未跟踪文件污染本次构建。Git 仓库 owner 与当前运行用户不一致触发 “dubious ownership” 错误。runner 上残留的全局 Git 配置影响 token 使用。对于自托管 runner建议保留默认的clean: true并在 runner 环境中统一用户权限。如果仓库目录由 root 创建而 job 以普通用户运行可以依赖set-safe-directory参数或手动执行git config --global --add safe.directory /workspace来解决。5. 常见问题排查从日志倒推原因5.1 先看日志里的三个关键字拿到 GitHub Actions 失败任务后先不要急着改参数。展开Checkout这个 step 的原始日志优先搜索三处内容remote url确认检出的仓库地址是否正确。fetch或receive关键字确认拉取的是哪个 ref。fatal或Error确认报错的具体原因。大多数检出问题都能从这三类信息里找到线索。5.2 Repository not found 或 403现象remote: Repository not found. fatal: repository https://github.com/owner/private-repo.git/ not found可能原因检出的仓库不存在或路径写错。仓库是私有的但GITHUB_TOKEN没有权限。使用了过期的 PAT。检查方式确认repository参数写的是owner/repo。确认用到的 PAT 是否过期。确认 PAT 是否勾选了repo或contents: read权限。如果目标是私有仓库确认当前账号是否有访问权限。解决方案- uses: actions/checkoutv4 with: repository: owner/private-repo token: ${{ secrets.PRIVATE_REPO_TOKEN }}预防建议为不同仓库创建独立 secret不要把所有 PAT 混在一起使用。5.3 fetch-depth 引起的 git diff 报错现象fatal: ambiguous argument HEAD~1: unknown revision or path not in the working tree.原因默认fetch-depth: 1只有最新一条提交记录HEAD~1指向的父提交不存在。检查方式在报错 step 前添加一行临时命令git rev-parse HEAD git rev-list --count HEAD如果--count输出1说明只有一条提交历史。解决方案把fetch-depth改为0或者改成足够大的值。若只是需要比较最近一次提交也可以改用git diff HEAD^ HEAD并确保有父提交。5.4 自托管 runner 上的 dubious ownership 错误现象fatal: detected dubious ownership in repository at /workspace原因Git 检测到仓库目录的 owner 与当前 Git 进程用户不一致。常见于容器内以不同 UID 运行或者 runner 工作目录由其他用户创建。检查方式执行ls -ld /workspace查看目录 owner再执行id查看当前用户。解决方案使用actions/checkout内置的set-safe-directory参数多数新版本默认开启。在 job 开头加入- run: git config --global --add safe.directory /workspace更根本的方式是统一容器和 runner 的用户 UID。5.5 子模块或 LFS 文件缺失现象子模块目录为空。LFS 文件内容变成类似version https://git-lfs.github.com/spec/v1的文本指针。原因没有设置submodules。没有设置lfs。子模块是私有仓库但没有给 token 授权。浅克隆状态下 LFS 对象没有被拉取。检查方式查看仓库根目录是否有.gitmodules。执行git submodule status。打开 LFS 文件看是否是指针文本。解决方案- uses: actions/checkoutv4 with: submodules: recursive lfs: true fetch-depth: 0 token: ${{ secrets.MY_TOKEN }}如果子模块 URL 是https://github.com/owner/private-module.git对应 token 必须有该仓库的读取权限。5.6 同一个 job 里切换分支时怎么选有些流程需要在同一个 job 里先后处理多个分支或标签。比如先检出 main 安装依赖再切换到发布标签执行发布脚本。推荐做法是在一开始就能确定目标 ref 的场景下直接用ref参数- uses: actions/checkoutv4 with: ref: refs/tags/v1.0.0只有当同一个 job 内确实需要多次切换工作区时才在后续步骤中使用git checkout。此时要特别注意浅克隆的问题git fetch --tags git checkout v1.0.0不先git fetch --tags本地很可能没有该标签对应的提交。这就是“git checkout problem 如何选择”的典型答案获取代码这一步交给actions/checkout仓库内部切换分支再考虑git checkout能通过ref参数提前确定的不要拖到后面手动切换。6. 最佳实践清单与工程化建议6.1 参数选型清单场景推荐配置常规 CI 构建使用默认参数即可需要比较分支差异fetch-depth: 0需要 PR 源分支代码ref: ${{ github.head_ref }}需要检出多个仓库配置repository和path仓库包含子模块submodules: recursive并提供对应 token仓库使用 Git LFSlfs: true必要时fetch-depth: 0后续不需要写回仓库persist-credentials: falsemonorepo 只需部分目录使用sparse-checkout自托管 runner 跨用户复用保留默认clean并确认set-safe-directory6.2 安全建议优先使用GITHUB_TOKEN少用长期有效的 PAT。在仓库的 Settings 中按最小权限原则配置 Workflow permissions。token 必须通过 secrets 注入不要直接写到 YAML 文件里。如果使用 PAT限制权限范围、设置有效期并定期轮换。不要把自托管 runner 暴露给不可信仓库因为 checkout 会执行仓库中的脚本和 Git 操作风险较高。6.3 性能建议默认浅克隆已经够用时不要随意改成fetch-depth: 0。只需要部分目录时优先使用sparse-checkout减少拉取数据量。同一个 job 内不要重复 checkout 同一仓库。需要子目录时用一次 checkout 加 path或者后续用working-directory定位。在自托管 runner 上clean: true和缓存机制需要一起设计。清理工作区会防止残留污染但也会减少增量构建的收益。6.4 版本锁定与维护生产环境不要直接使用actions/checkoutmain这类不稳定引用应该固定到 major 版本例如actions/checkoutv4。如果对供应链安全要求很高可以把 action 固定到完整 commit SHA- uses: actions/checkoute2b0e1a6c0f0e2c2a1e6d4e0c1a3e5d6f7a8b9c0这里只是示例格式实际要从官方 releases 页面复制对应 SHA。锁定 SHA 可以防止 action 仓库被恶意维护者植入变更但需要自己跟踪上游更新。6.5 把“如何选择”变成一条判断规则回到开头的混淆点在 workflow 中获取代码选择actions/checkout在已有仓库里切换分支选择git checkout。需要检出的目标 ref尽量在actions/checkout的ref参数中声明而不是在后续 step 里手动git checkout。原因在于actions/checkout会同时处理认证、工作目录、HEAD 状态和凭据持久化手动git checkout反而容易因为浅克隆、未 fetch、凭据丢失等问题翻车。写 workflow 时建议先跑一个最小示例把actions/checkout这一步的日志展开读一遍重点关注 remote url、fetch 的 ref 和最终 HEAD。这三项清楚了大部分检出问题就都有方向了。下一步可以继续学习actions/cache、actions/upload-artifact结合本文的参数选型把 CI 的缓存、产物和检出流程串起来。
返回列表