流水线在 checkout 之后突然报出 bad object、missing blob 或 pack checksum mismatch,重跑一次却可能恢复,也可能让更多任务同时失败。这类现象通常不是业务代码错误,而是工作区、对象缓存或 Git 包文件已经不一致。正确处理顺序不是立刻清缓存,而是先停止写入、保存现场,再判断损坏位于单个仓库还是共享层。
先区分网络失败与对象损坏
网络失败多发生在获取阶段,日志通常包含连接中断、远端提前关闭或传输未完成。对象损坏则常在检出、合并、读取历史或生成归档时出现,并带有具体对象哈希。先记录失败命令、退出码、提交号、工作区路径和缓存代次,不要只保留流水线最后几十行。
可以先执行只读检查:
git status --porcelain=v2
git rev-parse --show-toplevel
git rev-parse HEAD
git fsck --full --strict --no-dangling
git fsck 报告 missing blob,表示某个引用可达的文件对象不存在;missing tree 往往会阻断目录检出;invalid sha1 pointer 则说明引用指向不可读取对象。若只看到 dangling 对象,不应直接判定仓库损坏,它可能来自变基、压缩或被替换的提交。
诊断阶段的目标是确定损坏边界,而不是让当前命令暂时变绿。任何会重写对象库的操作,都应推迟到现场完成保存之后。
冻结工作区并保存证据
先暂停会写入同一目录的 runner、定时拉取和缓存更新任务。不要在原目录执行 git gc、git prune、重新打包或递归删除。它们可能改变对象布局,使最初的异常无法复现。
至少保存以下内容:完整任务日志、.git/HEAD、.git/config、.git/packed-refs、.git/refs、.git/logs、当前状态和未提交差异。源码工作区可能含未跟踪文件,打包前应查看列表,避免把凭据或大型构建产物一并归档。
incident="$HOME/git-incidents/$(date +%Y%m%d-%H%M%S)"
mkdir -p "$incident"
git status --porcelain=v2 > "$incident/status.txt"
git show-ref --head > "$incident/refs.txt"
git reflog show --all --date=iso > "$incident/reflog.txt"
git diff --binary > "$incident/worktree.patch"
git diff --cached --binary > "$incident/index.patch"
git fsck --full --strict > "$incident/fsck.txt" 2>&1 || true
如果工作区由 CI 随时重建,最稳妥的做法是把整个目录改名隔离,而不是在原位修补。隔离目录应设为只读,并记录其对应任务与缓存来源。
定位松散对象、包文件与共享缓存
Git 对象可能以松散文件存在,也可能收进 .pack。错误给出哈希时,可先查询对象类型:
object="0123456789abcdef0123456789abcdef01234567"
git cat-file -t "$object"
git cat-file -s "$object"
若命令失败,检查 .git/objects/${object:0:2}/${object:2} 是否存在。文件存在但无法读取,通常说明内容截断、校验不符或底层复制不完整。不要从另一个不确定来源复制同名文件覆盖现场。
对于包文件,逐个验证索引:
for index in .git/objects/pack/*.idx; do
git verify-pack -v "$index" >/dev/null || printf '%s\n' "$index"
done
如果仓库配置了 alternates,还要查看 .git/objects/info/alternates。多个任务共享同一个可写对象目录时,一次中断写入可能影响所有引用该目录的工作区。此时单独重新克隆某个仓库并不能消除根因,必须停止发布该缓存代次。
检查损坏是否可重复
复制隔离目录后再复现检查,并记录文件系统剩余空间、进程退出方式和同一时间段的任务数量。若每次都在同一对象失败,优先调查对象源与缓存;若失败对象不断变化,则应检查磁盘压力、并发写入和工作区清理逻辑。
用干净克隆恢复而不是原地手术
对没有人工修改的 CI 工作区,安全恢复路径通常是创建全新目录、完整获取目标提交、执行校验,再原子替换旧目录。不要让新克隆复用尚未确认的对象缓存。
root="$HOME/ci-workspaces"
next="$root/project.next"
active="$root/project"
failed="$root/project.failed"
rm -rf "$next"
git clone --no-local "$REPOSITORY_PATH" "$next"
git -C "$next" checkout --detach "$EXPECTED_COMMIT"
git -C "$next" fsck --full --strict
test "$(git -C "$next" rev-parse HEAD)" = "$EXPECTED_COMMIT"
mv "$active" "$failed"
mv "$next" "$active"
替换前还应运行项目最小验收,例如解析工程、列出构建入口或执行一组快速测试。若隔离仓库存在未推送提交,不要假设重新克隆能找回它们。应根据保存的引用与 reflog,在副本中尝试读取;对象确实缺失时,只能从可信远端、其他完整克隆或原始工作成果重新恢复。
把对象校验纳入缓存发布流程
共享 Git 缓存不应被所有任务同时写入。更稳定的模型是“生成、校验、发布”:任务在临时目录构建新缓存,完成获取后运行 git fsck,校验通过才将目录改名为新的只读代次。正在运行的任务固定使用自己的代次,不跟随中途更新。
日常检查项可以收敛为四条:缓存发布前校验对象;任务工作区与共享基线分离;失败时保留对象哈希和缓存代次;清理脚本只删除不再被引用的完整代次。这样即使单次下载或进程退出造成不完整写入,影响也会停留在尚未发布的临时目录。
最后,应把恢复演练写成可执行脚本,并在无业务数据的测试仓库中验证。一次可靠的演练至少要证明:损坏缓存不会继续分发、目标提交能够从干净来源重建、未提交差异有独立保存路径、替换过程不会让两个任务同时写入同一工作区。
常见问题
发现 Git 对象损坏后,可以直接删除 .git/objects 中的异常文件吗?
不建议。应先停止写入、保存日志与引用快照,再隔离整个仓库。直接删除对象可能扩大引用缺失范围,也会破坏后续取证条件。
CI 仓库损坏时,原地执行 git gc 能修复吗?
不能把 git gc 当作修复命令。它会重写和清理对象,可能让现场更难判断。可丢弃的 CI 工作区通常应重新克隆,并在验收后原子替换。
怎样避免损坏的 Git 缓存继续污染新任务?
缓存应使用只读基线、临时副本和校验后发布的流程。新缓存先在独立目录执行 git fsck,通过后再用目录重命名原子切换。
为持续构建选择一台云端 Mac
比较 SDKMac M4 与 SDKMac M4 Pro 的配置、区域和四种计费周期,再创建订单。