先跑通只读镜像再切换日常提交——Scion 接管 Git 仓库的边界

文章导读
能不能分阶段接管,取决于你是否把「只读」和「写入」当成两个独立准入点,而不是一次切换的两个步骤。做法通常是:先用一份复制出来的仓库副本跑只读,原始 Git 目录原封不动,只读对账通过后再开放日常提交;任一阶段出问题,整段退回 Git 侧即可,不需要先修好 Scion。下面按这两个阶段的边界展开,涉及 Scion 的命令行一律用占位形式,请按你所装版本的实际参数替换。
📋 目录
  1. 壹 复制仓库做只读镜像,原始 Git 目录保持不动
  2. 贰 在只读状态下执行读取类操作并逐项对账
  3. 叁 给切换写入设三个准入条件
  4. 肆 演练一次回退:把日常提交切回 Git 侧
  5. 伍 把本阶段失败的命令与报错原文存档
A A

能不能分阶段接管,取决于你是否把「只读」和「写入」当成两个独立准入点,而不是一次切换的两个步骤。做法通常是:先用一份复制出来的仓库副本跑只读,原始 Git 目录原封不动,只读对账通过后再开放日常提交;任一阶段出问题,整段退回 Git 侧即可,不需要先修好 Scion。下面按这两个阶段的边界展开,涉及 Scion 的命令行一律用占位形式,请按你所装版本的实际参数替换。

适用场景:已有在用 Git 仓库,准备让 Scion 承担日常读写,但担心一次性切换后退不回来。操作动作:复制出只读镜像、对账读取结果、满足三个准入条件后再开写入,并提前演练一次回退。验证方式:用 ref 列表、提交计数、文件内容哈希与原仓库逐项比对,回退后确认提交未丢失。风险边界:只读阶段验证的是「读取一致」,不等于「写入语义一致」;回退路径必须实际跑过,纸面方案不算数。所有结论仅限本次副本上的观察。

复制仓库做只读镜像,原始 Git 目录保持不动

这一步的唯一目的是建立随时可退回的基线:原始目录只要没被写过,退回就是零成本。建议先划清三个路径,避免后面分不清谁在读谁在写。

  • 原仓库(保持不动):/srv/git/origin/foo.git,仍由现有 Git 侧服务与钩子处理,权限收紧为只读给 Scion 侧账号。
  • 只读镜像(本次接管对象):/srv/scion/mirror/foo.git,Scion 只在这上面跑。
  • 状态与日志:/srv/scion/state/,放配置、启动日志和后面要存档的报错原文。

复制命令骨架,二选一即可。整体复制能保留钩子配置和自定义 ref,镜像克隆更干净但会丢掉仓库目录里的杂物:

# 方式一:整目录复制,保留仓库目录内所有文件
cp -a /srv/git/origin/foo.git /srv/scion/mirror/foo.git

# 方式二:按镜像语义克隆
 git clone `--mirror` /srv/git/origin/foo.git /srv/scion/mirror/foo.git
 git -C /srv/scion/mirror/foo.git remote set-url origin /srv/git/origin/foo.git

复制完先留一份原仓库的指纹,后续每次怀疑被改动都能拿它对:

git -C /srv/git/origin/foo.git rev-parse HEAD
 git -C /srv/git/origin/foo.git for-each-ref `--format`='%(refname) %(objectname)' | sort | sha256sum
 git -C /srv/git/origin/foo.git status `--porcelain`
 stat -c '%y %n' /srv/git/origin/foo.git/HEAD

把上面四行输出抄进存档文件并记时间。之后任何阶段只要 status `--porcelain` 出现非空输出,或 ref 哈希对不上,就说明原仓库被动过,此时应停掉只读阶段、排除是谁在写,不要继续往下走。

先跑通只读镜像再切换日常提交——Scion 接管 Git 仓库的边界

在只读状态下执行读取类操作并逐项对账

只读阶段的通过标准只有一条:同样的读取请求,在原仓库和镜像上得到一致结果。建议按「历史 → 分支与标签 → 文件内容」三层逐步对账,别一上来就比大文件。

# 历史
 git -C <repo> log `--oneline` -n 20
 git -C <repo> rev-list `--count` `--all`
 git -C <repo> cat-file -t HEAD

# 分支与标签
 git -C <repo> for-each-ref `--format`='%(refname) %(objecttype) %(objectname)' | sort

# 文件差异与内容
 git -C <repo> diff `--stat` HEAD~1 HEAD
 git -C <repo> show HEAD:<path> | sha256sum

把两边的输出分别重定向到文件再 diff,比人眼扫更可靠。对不上时,按下面顺序排查,通常能定位到八成问题:

  1. 确认两边看的是同一个 ref:git rev-parse HEAD 和分支名是否一致,别一边看分支一边看游离 HEAD。
  2. 确认镜像是否完整:用 `--mirror` 克隆还是普通克隆、是否带了浅克隆或对象过滤,浅克隆缺少历史会让 rev-list `--count` 偏小。
  3. 确认标签、附注 ref、隐藏 ref 是否都同步到,for-each-ref 的输出行数是否相等。
  4. 确认子模块目录是否为空,子模块指针一致但内容缺失是常见误判来源。
  5. 确认行尾与编码:仓库开了 autocrlf 或 .gitattributes 转换时,工作区内容哈希可能不同,此时应比较 Git 对象而不是工作区文件。
  6. 以上都一致仍对不上,再怀疑 Scion 自身的读取缓存,重启只读实例后重新对账,并把两次输出一起存档。

这里只验证了「读得对」,不包括写权限、写并发、钩子行为。不要用只读阶段的通过结果去推断写入阶段的安全边界。

给切换写入设三个准入条件

把「能不能开始写」变成三个可以回答是或否的问题,任一为否就先不切。

条件一:只读输出与原仓库一致

验证动作:重跑上一节的三层对账,ref 列表与提交计数完全一致,抽 2 至 3 个历史的文件内容哈希一致。不满足时:回到只读阶段继续排查,不要因为「差异很小」而带病切换。

先跑通只读镜像再切换日常提交——Scion 接管 Git 仓库的边界

条件二:提交粒度与写入规则已确认

验证动作:书面确认谁提交、提交作者与提交者字段怎么取、提交信息模板、分支命名规则、是否允许直接推主干、是否有提交前钩子或校验。可以先在镜像上手造一个测试提交,走完流程再删掉。不满足时:只开放不推主干的窄分支写入,或继续停留在只读。

条件三:回退路径已演练过

验证动作:按下一节完整跑一次回退并记录观察耗时。不满足时:不切写入,因为出问题时你没有可执行的退路,只有想法。

三个条件都满足后,建议先在一到两个低风险分支上开写入,观察一段实际使用情况,再放开主干。放开的范围要能用一句话说清,方便随时收回。

演练一次回退:把日常提交切回 Git 侧

回退演练要在只读阶段就做掉,而不是等出了事再试。骨架如下,具体命令按你的部署方式替换:

先跑通只读镜像再切换日常提交——Scion 接管 Git 仓库的边界
# 1. 停掉 Scion 写入侧入口,确认没有在途写入
<停止 Scion 写入服务的命令>
 ps -ef | grep -i scion | grep -v grep

# 2. 冻结镜像并记录当前 ref
 git -C /srv/scion/mirror/foo.git for-each-ref `--format`='%(refname) %(objectname)' | sort > /srv/scion/state/freeze-refs.txt

# 3. 把镜像上产生的提交推回原仓库(按约定选分支逐个推)
 git -C /srv/scion/mirror/foo.git push /srv/git/origin/foo.git <branch>

# 4. 恢复 Git 侧服务与钩子
<恢复 Git 侧服务的命令>

回退后确认提交没丢,至少做三件事:用 git -C /srv/git/origin/foo.git log `--all` `--oneline` 看被推回的分支头指向预期提交;用 git -C /srv/git/origin/foo.git rev-list `--count` <branch> 与冻结时的计数比对;用 git -C /srv/git/origin/foo.git fsck 检查对象完整性。若镜像上还有未推的分支或标签,先列出来再决定推回还是丢弃,不要直接删目录。

回退耗时只记录你这次实际观察到的值,并注明在当时的数据量和机器上测得,不要外推成承诺。别人问「切回去要多久」时,给的是记录,不是估计。

把本阶段失败的命令与报错原文存档

只读阶段一定会遇到几条报错,这些是下一阶段最省时间的线索。建议每条失败都存成一个独立文件,内容至少包含:

  • 完整命令行,包括工作目录和执行的账号。
  • stdout 与 stderr 原样粘贴,不摘录、不改写报错措辞。
  • 发生时间,以及当时的仓库状态:rev-parse HEAD、for-each-ref 输出、status `--porcelain`。
  • Scion 与 Git 的版本号,以及当时的配置片段(去掉凭据、密钥、内网地址)。
  • 是否可复现,以及复现时是否换了机器或换了副本。

复现所需的最小步骤单独写一段,目标是别人照着敲就能重现同一报错。写的时候从「一个干净镜像副本 + 一条命令」起步,逐步加参数,直到报错出现为止,最后把多余的中间步骤删掉。存档文件放在状态目录里,命名带上时间和仓库名,方便下一阶段按时间线回看。