git clone --mirror 创建带完整 refs 的裸仓库,适合上游快照备份;git init --bare 从零初始化裸仓库,适合纯下游镜像节点,二者均需手动配置 receive.denydeletes 等参数才能支持完整同步。

直接用单向镜像 + 定时拉取,90% 的跨国团队会遇到分支丢失、标签不同步、同步延迟超 10 分钟的问题。真正稳定的方案必须明确区分“谁推”“谁拉”“谁校验”,并把网络分区和权限隔离提前设计进架构里。
git clone --mirror 和 git init --bare 的本质区别
很多人以为 git clone --mirror 就是建了个“镜像”,其实它只是生成一个带完整 refs 的裸仓库(bare repo),但默认不设 receive.denycurrentbranch,也不开 receive.denydeletes —— 这意味着你不能直接往它上面 push,除非手动改配置。
git init --bare 则更干净:从零开始,无远程源、无历史引用,适合做“纯下游镜像节点”。而 git clone --mirror 更适合做“上游快照备份”,比如每天凌晨导出一次用于审计。
- 用
git clone --mirror后,必须执行git config --bool receive.denydeletes false才能同步删除的分支 - 用
git init --bare后,需手动git remote add origin <upstream-url>并设置fetch = +refs/*:refs/* - 两者都必须禁用
core.bare = true(默认已设),否则git push会报错 “refusing to update checked out branch”
同步策略选 push 还是 pull?看网络拓扑
主仓库在 GitHub 或 GitLab SaaS 上,你就只能用 pull 模式:镜像节点定时 git remote update --prune。如果主仓库在你自己的内网或云主机上,且可 SSH 登录,那优先用 push 模式 —— 在主仓库的 hooks/post-receive 里触发 git push --mirror mirror-server:/path/to/repo.git。
push 模式延迟低(秒级)、一致性高(原子推送),但要求主库可控;pull 模式更通用,但容易因网络抖动失败,且 --prune 不会自动清理已删除的远程跟踪分支,需额外加 git remote prune origin。
- GitHub/GitLab.com 用户 → 只能用 pull,配合 cron 每 3–5 分钟跑一次
git -C /path/to/mirror.git fetch origin '+refs/*:refs/*' && git -C /path/to/mirror.git remote prune origin - 自建 GitLab/Gitea 主库 → 推荐 push,用 post-receive 钩子,避免轮询浪费资源
- 混合环境(如主库在 AWS,镜像在阿里云)→ 用
git-remote-mirror工具,它封装了 fetch + push + prune 逻辑,比手写脚本容错强
多区域镜像节点间如何避免环形同步
三个镜像节点(新加坡、法兰克福、硅谷)如果都互相 git remote add,又都定时 git remote update,几小时后就会出现 ref 冲突、对象爆炸、甚至 git fsck 报错。根本解法是强制单向链路:只允许 A → B → C,禁止 C → A。
具体做法是在每个镜像节点的 config 文件中,把非上游 remote 的 fetch 行注释掉,只留 url 和 pushurl。再配合 git config --bool core.trustctime false 避免因文件系统时间不一致导致的同步跳过。
- 新加坡节点:只 fetch 主库(origin),push 给法兰克福(frankfurt)
- 法兰克福节点:只 fetch 新加坡(sg),push 给硅谷(sv)
- 硅谷节点:只 fetch 法兰克福,不配置任何 pushurl
- 所有节点统一关闭
gc.auto,改用夜间git gc --prune=now,防止同步中途被 gc 中断
为什么 git push --mirror 会丢标签?
不是丢,是没推全。默认 git push --mirror 只推 refs/heads/ 和 refs/tags/,但漏掉 refs/notes/、refs/pull/(GitHub PR 引用)、refs/replace/ 等特殊引用。企业级镜像必须显式覆盖:
git push --mirror --force \ --tags \ --push-option=notes \ mirror-server:/path/to/repo.git
注意:--push-option=notes 要求服务端 git 版本 ≥ 2.22,且服务器启用 receive.advertisePushOptions = true。否则 notes 会静默丢失,而你根本收不到错误提示。
- GitHub 镜像必须加
--push-option=notes,否则 code review 注释全丢 - GitLab CI/CD pipeline refs(如
refs/pipelines/)需单独git push mirror +refs/pipelines/*:refs/pipelines/* - 所有 push 命令建议加
--porcelain输出,并用grep -q "error\|fatal"做失败判断,别信返回值 0 就代表成功
最常被忽略的一点:镜像仓库的 git config core.sharedRepository 必须设为 group 或 all,否则多用户(如 Jenkins + 手动同步脚本)同时操作时,会因 .git/objects 权限冲突导致同步中断 —— 错误信息是 “unable to create temporary file” 或 “permission denied on .git/objects/incoming_XXXX”,但日志里往往只显示 “sync failed”,查半天才发现是 umask 问题。


















