Skip to content

初始化「源码拉取」失败:dirty 仓库导致 checkout 循环失败,网络与镜像错误掩盖根因 #311

Description

@1w1w11w1

结论(2026-07-29 现场复核)

该故障不是单一的“目标分支不存在”,而是多个问题叠加。此次持续重试仍无法恢复的直接根因是:本地 repo 存在未提交修改,git checkout -B 为保护本地改动而失败。实现没有检测 dirty 状态、没有记录 Git stderr,并把本地 checkout 冲突当成镜像故障继续轮换,最终只向用户显示“源码拉取失败/请检查网络”。

网络超时是真实存在的独立问题,会显著放大等待时间并产生错误诊断;但它不是本次所有重试都失败的唯一根因。

用户影响

场景 是否普遍 结果
干净的旧版本单分支浅克隆升级到新分支 否,不会必现 对照实验可正常 fetch + checkout
本地存在与目标分支冲突的未提交修改 条件性必现 checkout 100% 失败,重试/换镜像无效
前置 gh-proxy 在用户网络不可达或响应慢 较普遍,取决于网络 每个源最多等待 30/60 秒,并被误报为分支不存在
Git 命令超时 Windows 用户可能受影响 可能遗留 Git 子进程,继续占用网络/仓库并放大后续失败

因此:不是所有用户都会初始化失败;但错误分类、镜像排序和无反馈的串行超时会影响所有用户,dirty 仓库用户则会稳定进入不可自愈状态。

现场证据

1. CNB 拉取成功后仍在 checkout 阶段失败

用户手动指定 CNB 后,远端检查、refspec 配置和 fetch 均成功,随后立即失败:

07:50:52.478  检查目标分支通过
07:50:52.581  远程仓库配置完成
07:50:52.792  浅克隆配置完成
07:50:53.467  拉取最新提交完成
07:50:53.475  切换分支... - 90%
07:50:53.588  更新仓库失败: 切换分支失败
07:50:53.589  镜像源 CNB 官方镜像 操作失败

相同模式在 Cloudflare、Fastly、GitHub、CNB 等多个可用源上重复出现。网络和目标分支已经通过验证,换镜像无法修复本地 checkout 冲突。

2. 旧仓库是 dirty 状态,且修改与目标分支重叠

## release/v5.3.1
 M frontend/electron/main.ts
 M frontend/electron/services/environmentService.ts

本地改动是一个强制内置 Git 使用 schannel 的未提交热修;目标分支也修改了 frontend/electron/main.ts。因此以下命令会拒绝覆盖本地改动:

git checkout -B release/v5.4.0-beta.1 origin/release/v5.4.0-beta.1

checkoutBranch() 只检查退出码,丢弃 stderr,最终仅返回“切换分支失败”。

3. 干净浅克隆的跨分支升级可以成功

使用内置 Git 2.46.0,从干净的 release/v5.3.1 执行与应用相同的流程:

clone --single-branch --depth 1 release/v5.3.1  -> 成功
改写 remote.origin.fetch 到 release/v5.4.0-beta.1 -> 成功
fetch origin release/v5.4.0-beta.1 --depth=1       -> 成功
checkout -B release/v5.4.0-beta.1 origin/...       -> 成功
最终 dirty=0, HEAD=40790416...

这排除了“单分支浅克隆无法跨 release 分支升级”这一假设。

4. 清洁重克隆后完整初始化成功

将旧仓库完整备份后,从 CNB 清洁克隆目标分支,应用随后自动拉到 66d778c...。初始化 6 个步骤全部完成,依赖安装退出码为 0,后端启动、WebSocket 连接成功并进入主界面。

根因拆解

A. checkRepository() 把 dirty 仓库判为健康(本次硬失败的入口)

checkGitHealth() 只运行 git status 并检查退出码。dirty 仓库的退出码仍为 0,因此会进入更新路径,直到 checkout 才失败。

B. checkout 丢弃 stderr,且本地错误被当作镜像错误

checkoutBranch() 使用 stdio: 'pipe',但没有收集 stdout/stderr。上层 updateExistingRepository() 将错误压缩为“切换分支失败”,镜像轮替服务又继续尝试所有远端。

checkout 冲突是本地确定性错误,不应触发镜像轮换。

C. checkRemoteBranch() 混淆不存在、超时和传输错误

checkRemoteBranch() 对超时、非零退出码、spawn error 和真实无匹配分支都返回 false,上层统一显示“目标分支不存在”。

现场已确认 release/v5.4.0-beta.1 存在于 GitHub/CNB;日志中的部分“不存在”实际是超时、代理协议错误或镜像同步延迟。

D. 镜像顺序问题是错误分类,不是 JS 排序不稳定

默认配置顺序是 CNB -> gitee -> gh-proxy... -> GitHub,但实际执行顺序是:

Cloudflare -> Fastly -> EdgeOne -> ghfast -> GitHub -> CNB -> gitee

isGithubMirror() 会把 URL 中含 github.com 的所有 gh-proxy 识别为 GitHub 源,随后 sortMirrors() 将它们整体提升到 CNB/gitee 前面。

现代 Electron/V8 的 Array.sort() 是稳定排序,因此原 Issue 中“排序不稳定”的判断应撤回;这是当前分类/优先级逻辑的确定性结果。

E. 超时可能遗留 Git 进程

现场发现一组从 04:23 持续到 08:17 的 git.exe -> git.exe -> git-remote-https.exe 进程,其中 HTTPS 连接仍为 Established;关闭 AUTO-MAS 后仍未退出。

超时处理只调用 proc.kill(),不检查返回值、不等待整个进程树退出,也没有在 resolve/reject 前确认清理完成。Windows 下 Git 的 helper 子进程可能存活,继续占用网络或仓库,进一步放大后续超时和切换失败。

建议修复

P0:避免不可恢复的 dirty checkout 循环

  1. 更新前执行 git status --porcelain,把 clean/dirty 纳入仓库状态。
  2. repo 是应用管理的缓存仓库时,dirty 状态不要直接 reset --hard。将旧仓库带时间戳备份,再临时目录清洁克隆,校验后原子切换。
  3. checkout 捕获并记录 stderr、退出码与命令阶段;向用户明确提示“本地文件有修改,已备份并重建”或提供可执行恢复动作。
  4. 将错误分类为 networkremote_branch_missinglocal_changesrepository_corrupttimeout;只有网络/远端类错误才轮换镜像。本地错误立即停止。

P1:修复镜像和超时行为

  1. repo 镜像默认保留配置顺序,或显式排序为 CNB/gitee 优先;不要用 URL 是否包含 github.com 判断代理质量。
  2. checkRemoteBranch 返回结构化结果,不再用 boolean 合并所有失败。
  3. 超时后终止完整 Git 进程树并等待退出;记录 kill 结果和残留 PID。
  4. UI 显示“正在尝试镜像 x/y、当前阶段、已用时”,提供取消操作和合理的总超时预算。
  5. fetchLatestCommit 同样保留 stderr,并避免 60 秒无反馈等待。

验收测试

  • 干净的旧分支单分支浅克隆可升级到新 release 分支。
  • 存在与目标分支冲突的本地修改时,返回 local_changes,备份/重建,不尝试下一个镜像。
  • ls-remote 超时显示“网络超时”,真实无分支才显示“不存在”。
  • 超时后 Git 及 git-remote-https 子进程全部退出。
  • 默认 repo 镜像顺序与配置/明确优先级一致,CNB/gitee 不被 gh-proxy 隐式挤到末尾。
  • checkout/fetch 失败日志包含 Git stderr 和退出码。
  • 完整初始化完成后仓库 clean、目标分支正确、后端与 WebSocket 正常。

原始材料

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions