ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

GitHub Actions中actions/checkout与git checkout的区别及避坑指南

GitHub Actions中actions/checkout与git checkout的区别及避坑指南 先问一个问题你有没有在 GitHub Actions 里写过这样的流水线——明明 workflow 第一步就用了actions/checkout日志也显示 “Checkout complete”但下一步脚本一执行却告诉你ls: cannot access src/: No such file or directory。或者更常见的是你辛辛苦苦在 runner 上改了文件下一步运行却发现改动全部消失白干一场。如果你遇到过或者正在排查这个问题那么这篇文章就是写给你的。这篇博客会聚焦一个看似简单、实则非常容易踩坑的官方 Actionactions/checkout。我会讲清楚它和本地命令git checkout到底有什么区别它的默认行为有哪些坑怎么用with参数控制拉取方式以及当出现“目录不存在”“文件被清空”“子模块拉不下来”时应该按什么顺序排查。读完你至少能把 GitHub Actions 里的代码检出环节彻底搞明白从“能跑”升级到“知道为什么能跑”。1. 这篇文章真正要解决的问题先说判断actions/checkout是 GitHub Actions 里被使用频率最高、同时也被误解最深的官方 Action。很多 CI/CD 问题看似发生在后续的构建或测试步骤根子其实在第一步代码检出时就埋下了。为什么这么说因为它的名字里带着 “checkout”和 Git 本地命令git checkout太像了。很多有 Git 基础的开发者第一次看到这个 Action 时会下意识把它理解成“切换 Git 分支”的操作。但实际上在 CI 环境里runner 是一个临时的、全新的虚拟机或容器它一开始根本没有你的代码。actions/checkout做的是“把仓库代码拉取到 runner 上”而不是在已有仓库里“切换分支”。这两种理解导致的问题完全是两个方向如果按“拉取代码”来理解你会关注fetch-depth、token、path、submodules这些参数。如果按“切换分支”来理解你会纠结于ref的写法却忽略了 runner 上其实没有仓库这个前提。所以这篇文章真正要解决的是三类问题第一概念混淆问题。把actions/checkout和git checkout放在一起对比给你一个清晰的边界以后不会再在脑子里打架。第二默认行为不透明的问题。很多人不知道actions/checkout默认只拉取单个提交的代码fetch-depth: 1也不知道它默认会使用GITHUB_TOKEN进行鉴权更不知道它默认会执行clean操作把工作区里残留的文件清掉。这些默认行为单独看都合理组合在一起就是无数“灵异事件”的源头。第三生产环境配置问题。什么时候需要fetch-depth: 0什么时候必须配submodules: recursive私有仓库的依赖仓库怎么拉persist-credentials要不要关这些经验不踩几次坑是积累不下来的这篇文章一次性给你理清。如果你是 GitHub Actions 的中级使用者已经跑通了一些简单 workflow但对 checkout 的细节缺乏完整认知这篇文章尤其适合你。如果你是完全的新手建议先照着第 5 章的示例跑一遍再回头看原理效果更好。2. 从 git checkout 到 actions/checkout名字相近功能完全不同要理解actions/checkout必须先把它从git checkout这个“同名长辈”的阴影里拉出来。2.1 git checkout在已有仓库里切换状态git checkout是 Git 自带的一个本地命令核心作用是切换分支或恢复工作区文件。比如git checkout main git checkout -b feature/login git checkout -- src/main/java/App.java这三个命令分别做了不同的事切换分支、新建并切换到新分支、放弃某个文件的本地修改。它们的共同前提是你当前已经在一个完整的 Git 仓库里并且本地有完整的对象数据库。git checkout本质上是把你当前 HEAD 指针移动到一个新的位置并让工作区文件跟着变化。这个过程不涉及网络拉取除非配置了特殊的自动 fetch也不涉及克隆仓库。它就是本地状态切换。2.2 actions/checkout在全新环境里克隆仓库actions/checkout则是 GitHub 官方发布的一个 Action它运行在 GitHub 托管的 runner或者你自托管的 runner上。它的任务是在一个全新的、通常没有任何项目代码的环境里把指定仓库的指定版本代码拉到工作目录。我们直接看它的源码逻辑这是理解这个概念最直接的方式。它的核心执行过程大致是在 runner 上创建或进入一个空的工作目录。执行git init初始化一个空的 Git 仓库。添加远程仓库地址即origin。执行git fetch拉取指定ref对应的提交。执行git checkout --detach commit_id或git switch检出对应提交。根据参数决定是否配置token到.git/config、是否拉取子模块、是否清理工作区。看到第 5 步你会发现它内部确实也用了git checkout但这里的前提是它先完成了一个类似git clone的过程然后在临时仓库里做了一次孤儿检出来匹配指定 commit。这一切对用户是封装好的。2.3 为什么容易混淆混淆的根本原因有三个名字里都带checkout搜索引擎和 AI 工具都会把它们混在一起。很多教程在解释actions/checkout时会简单说“它相当于执行了git clone和git checkout”但这句解释其实省略了关键细节。在 Runner 上执行命令时你确实可以在 shell 里看到它调用了git checkout于是不熟悉的人会以为“这个 Action 就是封装了一个 checkout 命令”。为了彻底理清我用一个表格对比对比维度git checkoutactions/checkout运行位置本地已有仓库中GitHub Actions runner 的空白环境中核心功能切换分支、恢复文件拉取仓库代码并检出指定提交是否依赖已有仓库是必须在仓库内执行否自动完成 init/fetch/checkout网络行为通常不拉取新对象必须从远端 fetch 代码常见失败场景分支不存在、本地冲突token 无权限、ref 不匹配、fetch-depth 不足使用场景日常开发、切换需求分支CI/CD 流水线第一步为后续步骤准备代码一句话总结git checkout是“在已有代码里换一种状态”actions/checkout是“把代码先弄到 runner 上然后再进入指定状态”。3. actions/checkout 的工作原理与默认行为现在进入正题。我们要理解的不是“它怎么做”而是“它默认做了什么以及这些默认行为带来了什么后果”。3.1 最小用法做了什么一个最简单的 workflowname: checkout-demo on: push jobs: build: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkoutv4 - name: List files run: ls -la这个uses: actions/checkoutv4执行时runner 上会发生这些事创建$GITHUB_WORKSPACE工作目录。在该目录中初始化一个临时 Git 仓库。将github.repository即你当前的仓库配置为origin。以GITHUB_TOKEN为凭据从中获取当前分支/提交的最新代码。检出代码到工作区并切换到 detached HEAD 状态。不配置任何额外用户信息因为后续 Action 可以靠各自的 token 访问仓库。看起来一切正常但注意第 5 步末尾的 “detached HEAD”。这意味着即使你在后续步骤里执行git commit也不会自动提交到某个分支上。这是新手经常忽略的一个点——CI 环境里直接改代码再想提交回仓库不能只靠 checkout。3.2 fetch-depth 默认值的影响输入材料里常见的fetch-depth默认值是1。也就是说actions/checkout默认只拉取当前 ref 对应的那一条 commit 及其关联文件。这带来两个影响优点是快。流水线不需要拉取完整 Git 历史尤其是对于历史很长、提交量很大的仓库省时省流量。缺点是如果你想在后续步骤里做依赖版本对比、生成变更集diff、git log 分析或者需要读取历史 commit 的信息就会失败。因为这些数据根本没有被 fetch 下来。所以当你看到下面这种报错fatal: ambiguous argument HEAD^: unknown revision or path not in the working tree.大概率就是 fetch-depth 太浅无法访问父提交。3.3 默认 token 的权限边界actions/checkout不配置 token 时会使用 Actions 自动生成的GITHUB_TOKEN。这个 token 的权限由仓库 Settings - Actions - General - Workflow permissions 控制默认是Read and write permissions但不同组织可能有不同的策略。这个默认 token 的特点是只在当前 workflow 运行期间有效。只能访问当前仓库或者是触发 workflow 的那个仓库。如果 workflow 是pull_request事件触发的token 权限会被限制为只读且无法修改 PR 来源分支。如果你要 checkout 的是另一个私有仓库用默认 token 是拉不下来的。第二个场景非常常见你的主仓库引用了一个私有的模板仓库或依赖仓库想通过actions/checkout把它一起拉下来。这时默认 token 没有权限会报错remote: Repository not found. fatal: repository https://github.com/your-org/private-repo.git/ not found解决办法是使用一个具有目标仓库读取权限的 PATPersonal Access Token或者配置secrets。这个后面在示例章节会详细演示。3.4 clean 与后续步骤的交互actions/checkout的clean参数默认值为true。它会在拉取代码之前清空工作目录中与 Git 无关的所有文件。这个设计的初衷是保证 runner 环境纯净防止上一次构建的残留文件干扰本次构建。但它也是一个“坑”的来源如果你在 workflow 的某一步用脚本生成了构建产物或者修改了文件另一步又再次执行了actions/checkout有些人会在多个 job 中重复 checkout那么上一次的修改可能会被清除。更准确地说clean: true会删除工作区里未跟踪的文件但对已被 Git 跟踪的文件的修改会被下一个步骤的 checkout 覆盖。也就是说如果你希望“先在 runner 上缓存/生成一些文件然后再 checkout 代码”需要设置clean: false。如果你使用多个 job并且希望把构建产物从一个 job 传给下一个 job通常不能用重复 checkout而应该用actions/upload-artifact。4. 核心配置项详解actions/checkout的配置全部集中在with字段里。我按使用频率从高到低逐一说明。4.1 repository指定要检出哪个仓库。默认值是${{ github.repository }}也就是当前触发 workflow 的仓库。大多数情况下不需要改。但如果你需要在 workflow 中同时检出另一个仓库比如文档站要引用多个仓库的内容可以这样写- name: Checkout docs repo uses: actions/checkoutv4 with: repository: your-org/docs token: ${{ secrets.DOCS_REPO_TOKEN }} path: docs这个组合非常常见repository指定目标仓库token提供跨仓库权限path指定检出到工作区下的哪个子目录。4.2 ref指定要检出的分支、标签或提交 SHA。默认值是${{ github.ref }}即触发 workflow 的分支或标签。一个典型场景当 workflow 由pull_request事件触发时github.ref是refs/pull/number/merge合并后的 ref而不是源分支。如果你希望检出源分支本身可以覆盖- name: Checkout source branch uses: actions/checkoutv4 with: ref: ${{ github.head_ref }}head_ref在 PR 事件中代表源分支名。注意head_ref在 push 事件中为空所以如果需要兼容两种事件建议用上下文判断。4.3 token用于远程 git 操作的鉴权。默认是${{ github.token }}。如果需要跨仓库访问或者默认 token 权限不够需要传入一个有权限的 PAT 或者配置好的 secret。- name: Checkout with PAT uses: actions/checkoutv4 with: token: ${{ secrets.MY_PAT }}这里需要提醒一下安全边界不要把 PAT 硬编码在 workflow 文件里一律放到仓库或组织级的 Secrets 中并在 Secrets 中配置最小权限只读某个仓库的权限、不勾选不必要的 scope。4.4 fetch-depth控制拉取 Git 历史的深度。默认是1。fetch-depth: 0表示获取全部历史。fetch-depth: 1只获取最新一条 commit 及其包含的文件快照。fetch-depth: N获取最近 N 条 commit。需要知道的是fetch-depth: 0并不只是“多拉点历史”这么简单。它会影响后续所有依赖 Git 历史的步骤例如git diff HEAD^ HEAD能跑通。某些语义化版本计算工具如 semantic-release能正常工作。子模块遍历时能获得更准确的状态。代价是拉取时间变长、仓库变大。建议按需设置不要无条件全部拉取。- name: Fetch all history uses: actions/checkoutv4 with: fetch-depth: 04.5 path指定代码检出到工作区的哪个子目录。默认是仓库根目录。- name: Checkout to subdirectory uses: actions/checkoutv4 with: path: my-project之后你在后续步骤中访问代码时路径就是$GITHUB_WORKSPACE/my-project。如果你要在同一个 job 中检出多个仓库并相互引用这个参数是必须的。4.6 clean默认true表示在检出前清空工作区中未被 Git 跟踪的文件。如果你知道自己后续要在 runner 上生成一些文件且不希望被 checkout 清理可以设置为false。- name: Checkout without clean uses: actions/checkoutv4 with: clean: false4.7 submodules是否检出子模块。可选值有true、recursive、false。默认false。如果你的仓库使用 Git Submodule且构建过程需要子模块代码必须设置- name: Checkout with submodules uses: actions/checkoutv4 with: submodules: recursiverecursive会递归拉取嵌套子模块。对于私有子模块仓库如果子模块 URL 是 HTTP 形式还需要配置token否则拉取权限不足。4.8 persist-credentials默认true会把 token 保存到.git/config中的http.https://github.com/.extraheader这样后续git push或git fetch可以使用同样凭据。在安全要求严格的场景比如不希望后续步骤中的任意脚本利用该 token 访问仓库可以设置- name: Checkout without persisting credentials uses: actions/checkoutv4 with: persist-credentials: false设置后如果要 push 回仓库需要手动配置凭据否则会失败。这是一个安全与便利的取舍。4.9 sparse-checkout启用稀疏检出只拉取指定路径下的文件。适合大型 monorepo 中只需要构建某个子目录的场景。- name: Sparse checkout uses: actions/checkoutv4 with: sparse-checkout: | apps/api packages/shared注意sparse-checkout需要fetch-depth与 Git 版本的支持GitHub 托管的 runner 上通常没有问题。4.10 lfs是否下载 Git LFS 对象。默认false。- name: Checkout with LFS uses: actions/checkoutv4 with: lfs: true如果项目使用大文件存储且影响构建必须开启。5. 环境准备与最小示例我们不需要本地安装任何 GitHub Actions 相关工具只需要一个 GitHub 仓库和可用的网络。5.1 准备仓库第一步在 GitHub 上创建一个新的测试仓库例如命名为checkout-demo并提交一个简单的文件mkdir checkout-demo cd checkout-demo git init -b main echo # Checkout Demo README.md mkdir -p src echo console.log(hello from checkout demo); src/index.js git add . git commit -m Initial commit git remote add origin https://github.com/your-username/checkout-demo.git git push -u origin main5.2 创建 workflow在仓库根目录创建.github/workflows/checkout-demo.ymlname: checkout-demo on: [push] jobs: demo: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkoutv4 - name: Show workspace files run: pwd ls -la - name: Show git status run: git status - name: Show current branch run: git branch -a提交并推送这个 workflow 文件后前往仓库的 Actions 页面可以看到一次新的运行。5.3 验证运行结果运行结束后点开Show git status这一步你大概率会看到类似输出HEAD detached at commit-sha nothing to commit, working tree clean这说明actions/checkout确实是在一个 detached HEAD 状态下检出了代码。你看到的这个 commit SHA就是触发 workflow 的那次 push 的最新 commit。再点开Show current branch你会发现问题git branch -a的输出只包含* (HEAD detached at sha)根本看不到main或remotes/origin/main。这是fetch-depth: 1的典型表现本地没有完整 refs只有当前 commit 数据。如果你想看到分支信息需要在 checkout 中设置fetch-depth: 0- name: Checkout code uses: actions/checkoutv4 with: fetch-depth: 0改完后重新运行git branch -a就能看到远程分支列表了。这个差异非常直观地展示了fetch-depth的意义。6. 完整示例从拉取代码到构建验证下面我们用一套更接近生产环境的例子把actions/checkout放在一个完整的 Java 项目流水线中演示。假设项目是一个 Maven 工程目标是通过 CI 构建并运行测试。我们设计三个场景场景 A普通主分支构建浅检出就够了。场景 B需要生成两次提交之间的 diff 报告必须完整历史。场景 C需要同时拉取文档仓库路径隔离。6.1 场景 A浅检出 构建测试name: java-build on: push: branches: [ main ] pull_request: jobs: build: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkoutv4 - name: Set up JDK 17 uses: actions/setup-javav4 with: java-version: 17 distribution: temurin - name: Build and test run: mvn -B clean verify在这个例子中actions/checkout默认的fetch-depth: 1就足够了因为 Maven 构建不需要 Git 历史。这样做速度最快。6.2 场景 B完整历史 生成 diff如果你想在 PR 中展示文件变更列表或者自动判读是否需要更新文档需要完整历史name: diff-report on: pull_request: jobs: diff: runs-on: ubuntu-latest steps: - name: Checkout with full history uses: actions/checkoutv4 with: fetch-depth: 0 - name: Generate diff run: | git diff origin/${{ github.event.pull_request.base.ref }}...${{ github.sha }} --name-only这里的关键是fetch-depth: 0确保我们能访问 base 分支的 commit。没有它git diff会报错。6.3 场景 C多仓库检出如果你的 CI 需要在本次构建中使用另一个文档仓库的内容name: multi-repo on: [push] jobs: build: runs-on: ubuntu-latest steps: - name: Checkout main repo uses: actions/checkoutv4 - name: Checkout docs repo uses: actions/checkoutv4 with: repository: your-org/docs token: ${{ secrets.DOCS_REPO_TOKEN }} path: docs - name: Verify both repos run: | ls -la ls -la docs这里DOCS_REPO_TOKEN必须是一个有docs仓库读取权限的 secret。如果你没有配置第二步就会因为权限不足而失败。需要提醒的是actions/checkout在检出第二个仓库时会把当前 job 的GITHUB_WORKSPACE作为父目录然后在其下创建docs子目录所以不会污染主仓库的检出内容。6.4 如何判断成功每个场景成功与否可以直接看 workflow 的绿色对勾。但更重要的判断方式是观察日志里是否有这些关键行Checkout complete以及后续命令是否输出了预期内容。如果docs目录不存在ls -la docs会直接非零退出导致 job 失败从而暴露问题。7. 常用组合场景与进阶写法7.1 子模块仓库现代项目越来越普遍地使用子模块管理共享库。这时 checkout 配置要复杂一些- name: Checkout with submodules uses: actions/checkoutv4 with: submodules: recursive token: ${{ secrets.SUBMODULE_TOKEN }}为什么还需要传 token因为子模块的 URL 如果写的是https://github.com/org/private-repo.gitrunner 上的 git 会尝试匿名访问私有仓库直接 404。传入 token 后actions/checkout会将其注入到 git 请求头中才能通过鉴权。这里有一个工程建议子模块的 URL 尽量使用相对路径写法例如../../org/private-repo.git这样 GitHub 会自动基于当前仓库的 base URL 解析可以在多个 fork 仓库间通用。但如果你需要自托管 Git 实例这个方案不完全适用还是需要 token。7.2 分支策略与 ref 选择当 workflow 需要同时处理多个分支或 tag 时ref参数需要设计好。举例你希望在release/1.0分支上触发构建时能拉取main分支上最新的配置文件。可以- name: Checkout release branch uses: actions/checkoutv4 with: ref: ${{ github.ref }} - name: Checkout config from main uses: actions/checkoutv4 with: repository: ${{ github.repository }} ref: main path: .config-from-main clean: false但这个写法的隐患是同一 job 中两次 checkout 同一个仓库第二次可能因为第一次 checkout 产生的.git目录冲突。所以如果是同一仓库的不同分支建议直接用后续步骤的git fetch和git show操作而不是多次 checkout。7.3 与 actions/upload-artifact 配合一个常见的误区是把actions/checkout写在了多个 job 中并期望构建产物跨 job 保留。这是不成立的。每个 job 都是全新的 runner 环境。正确做法只在构建 job 中 checkout 并构建然后使用actions/upload-artifact上传产物后续部署 job 中下载产物而不是再次 checkout。jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - run: make build - uses: actions/upload-artifactv4 with: name: dist path: dist/ deploy: needs: build runs-on: ubuntu-latest steps: - uses: actions/download-artifactv4 with: name: dist这个设计更贴近生产环境也避免了“重复 checkout 导致文件被覆盖”的隐患。8. 常见问题与排查方法这里把最常见的报错和异常汇总成表方便你遇到问题时快速定位。问题现象可能原因排查方式解决方案checkout 成功但目录中没有代码workflow 使用了path参数代码在子目录中检查path配置执行ls -la在后续步骤中使用正确路径或去掉path参数fatal: ambiguous argument HEAD^fetch-depth 太小缺少父 commit查看 checkout 日志中的Fetching...深度设置fetch-depth: 0remote: Repository not found目标仓库是私有仓库token 无权限检查 token 权限和仓库可见性使用有权限的 PAT secret 并配置token构建后文件被修改但下一次运行时改动丢失clean: true清除了未跟踪文件/覆盖工作区检查是否在 checkout 前修改了文件在自定义文件生成步骤之前避免重复 checkout或设置clean: false子模块目录为空未设置submodules检查子模块目录是否为空、日志是否有子模块报错设置submodules: recursive私有仓库配置 token在 pull_request 中无法 push 回源分支GITHUB_TOKEN 在 PR 中只读查看 push 报错检查 workflow 权限使用 PAT 配置 token或调整分支保护策略checkout 这一步很慢fetch-depth: 0 且仓库历史很大查看日志耗时评估是否必须完整历史按需设置fetch-depth后续 job 中找不到上一个 job 生成的文件不同 job 是独立环境查看文件路径和 artifact 配置使用actions/upload-artifact/download-artifact下面单独展开两个值得细说的问题。8.1 checkout 成功但代码“不在”目录这个问题的核心原因几乎都是path参数。当你在with中配置了path: my-app后代码会放在$GITHUB_WORKSPACE/my-app而不是 workspace 根目录。很多初学者在后续步骤里执行ls -la看到根目录没有代码就会误以为 checkout 失败了。排查时先看日志里有没有这行Checking out the ref再看日志中是否有Path: my-app之类的工作目录信息。如果确实使用了子目录后续所有引用路径都要加前缀。这个坑在配置了多个仓库的场景里尤其明显。8.2 同一仓库二次 checkout 的问题有些人会在一个 job 里连续写两次actions/checkout以为这样可以分别在主目录和子目录各拿一份代码。但实际操作时第二次 checkout 清洗了第一次的内容导致第一个目录里的文件不完整。最稳妥的做法是一次 checkout 主仓库再用git fetch或actions/checkout的其他参数处理额外需求不要对同一个仓库做无必要的重复 checkout。9. 最佳实践与工程建议这部分是整篇文章最有复用价值的内容来自对大量真实项目经验的总结。9.1 版本策略在uses中尽量使用带主版本的引用如actions/checkoutv4。不要使用main或master因为上游更新可能引入不兼容变更。也不要锁定到某个具体的 patch 版本例如v4.1.1除非你有非常强的可复现需求。锁定主版本既能在一定周期内获得 bugfix又能避免破坏性变更。GitHub 官方每次发布新的主版本都会迁移文档建议在升级主版本前先阅读 release notes。9.2 权限最小化关于token的一条核心建议默认情况下优先使用GITHUB_TOKEN。不需要为了“能跑”而总是传 PAT。如果确实需要跨仓库读取为 PAT 配置尽可能少的 scope。比如只需要读取repo内容就不要给repo的写权限。将 PAT 存入 Organization Secrets 而不是仓库 Secrets方便统一控制和轮换。安全底线再次提醒任何形式的 token 都要通过 GitHub Secrets 注入绝对不要直接写在 workflow 文件里。9.3 fetch-depth 按需设置不要默认全仓fetch-depth: 0也不要默认浅检出。合理的判断标准是只做编译、测试、打包浅检出即可。需要生成 diff、分析历史提交、语义化版本计算完整历史。仓库非常大且只构建某个目录考虑sparse-checkout。性能是 CI 体验的一部分。一个 5 分钟的流水线里如果两分钟花在 checkout 完整历史上而业务只需要一次构建这个成本是不值得的。9.4 注意 PR 事件的 token 限制当 workflow 由pull_request触发时GITHUB_TOKEN分支保护机制是只读的它不能 write 到源分支。如果 CI 需要自动修复代码并 push 回 PR必须使用 PAT或者调整触发策略比如pull_request_target但这有安全风险不是默认推荐方案。处理 PR 时还有一点容易踩坑github.ref是 PR 合入后的 ref而不是源分支。如果你希望 checkout 源分支的代码进行更真实的测试需要设置ref: ${{ github.head_ref }}但这也会带来一些安全性顾虑比如依赖 PR 中的恶意 workflow 修改。建议只在信任的贡献者范围内使用。9.5 日志与可观测性actions/checkout的日志默认包含很多有用信息包括Remote URL / 目标仓库检出的 reffetch 深度是否启用 submodules是否使用 LFS排错时第一步永远是打开 checkout 步骤的完整日志查看这些配置项是否符合预期而不是直接去看后续步骤的报错。有时候后续步骤的报错只是因为入参没传对。9.6 自托管 runner 的差异如果你在自托管 runner 上使用actions/checkout注意runner 上的git版本必须支持 Actions 所需的功能比如sparse-checkout需要新版 Git。自托管 runner 不清空工作区clean: true会负责清理但如果多个 workflow 共用同一个 runner仍可能出现缓存污染。自托管 runner 的GITHUB_WORKSPACE可能被多个 job 复用建议在关键步骤前后都输出pwd确认路径。10. runner 内部发生了什么一次完整的时间线为了把整个流程讲透我用一个不涉及图表的时间线来概括一次 checkout 的执行过程。假设你在main分支上推送了一次提交。workflow 开始后runner 上的actions/checkoutv4执行环境准备阶段。runner 创建GITHUB_WORKSPACE目录并确保 shell 环境sh/bash可用。设置 Git 全局配置。actions/checkout会设置user.name和user.email为临时的 GitHub Actions 用户避免后续 git 操作因缺少身份而失败。初始化临时仓库。在 workspace 下执行git init并配置remote.origin.url为仓库地址。认证配置。根据传入的token值向 Git 请求头中写入Authorization: token token或者设置为不持久化。拉取代码。按照fetch-depth参数执行git fetch。这一步会从远程仓库拉取指定 ref 的 commit 数据。如果fetch-depth: 1只会拉取最新的一个提交快照如果是 0拉取全部历史。检出代码。执行类似git checkout --detach commit的操作把工作区内容更新到目标 commit。注意是 detached HEAD没有在本地创建对应的分支。处理子模块和 LFS。如果开启submodules或lfs在这一步执行相应的拉取。清理工作区。如果clean: true删除工作区中不被 Git 跟踪的文件确保构建环境干净。持久化凭据。如果persist-credentials: true在.git/config中写入 token供后续步骤使用。整个过程对外只体现为日志里的一小段输出但每一个参数都在悄悄影响最后的结果。这也是为什么我们排查问题时第一步要回到 checkout 的日志上去看。11. 总结与后续学习方向到这里actions/checkout的核心内容已经讲透了。我整理一下今天的要点actions/checkout是 GitHub Actions 的官方代码检出 Action主要负责在 runner 上拉取仓库代码并检出指定提交和本地命令git checkout是两个完全不同的概念。GitHub Actions 的 checkout 默认执行浅检出fetch-depth: 1只会拉取当前提交使用fetch-depth: 0才能拿到完整历史。跨仓库检出必须显式传入有权限的 token否则非常容易遇到 “Repository not found” 类报错。clean默认清理工作区submodules默认关闭这两个参数分别对应两种常见的构建污染和子模块缺失问题。排查问题时先看 checkout 步骤的日志确认 repository、ref、token、fetch-depth 等参数是否符合预期再往下游找原因。如果你的项目已经足够复杂下一步可以继续研究这些方向actions/checkout与actions/cache配合如何优化依赖安装耗时pull_request事件下的细粒度权限控制和分支保护策略自托管 runner 上的容器化配置以及和workflow_call结合的可复用 workflow 设计。最后给你一个可直接执行的建议如果今天什么都记不住那就先记住一句话——在 GitHub Actions 里遇到“明明检出成功但后续步骤看不到文件、看不到历史、拉不到子模块、push 不回去”这一类问题十有八九是 checkout 的配置参数没对齐。把这篇文章的常见问题表复制到你的团队文档里至少能少花半天排查时间。如果你是刚开始接触 GitHub Actions建议先不要追求把所有参数都配齐而是从fetch-depth: 1的浅检出开始跑通一个最小构建然后再逐步引入完整历史、子模块、多仓库等高级特性。这样既能控制 CI 成本也更容易定位问题出现的环节。
返回列表