前阵子我帮某团队的内部研发环境搭了一套 GitHub 镜像站。起因很简单:他们的构建服务器在一个受控网络分区里,不能直接访问外网,但线上服务依赖大量 GitHub 上的开源项目,每次发版前都要人工把代码包拷进去,既慢又容易漏。于是就需要一套方案:定期把指定的 GitHub 仓库完整同步到内网,让所有构建机和开发机都从内网地址拉代码。
这个需求听起来简单,真做起来却有不少细节——裸仓库、增量 fetch、LFS 对象、子模块、客户端 URL 改写、同步监控……我前前后后踩了不少坑,这里把完整过程整理成一篇可直接参考的全攻略。如果你也在为团队、离线环境或隔离网络搭建类似服务,或者想彻底搞懂 Git 镜像同步的底层机制,这篇应该能帮你少走很多弯路。
1. 先想清楚:你要的是镜像、缓存还是离线代码库
1.1 三种需求对应三种截然不同的架构
很多人一听到“镜像站”就默认是“把 GitHub 复制一份”。但复制一份也有不同的复制法,核心差别在于你到底要解决什么问题。
第一种是代码镜像:目标是把指定仓库完整同步到内网,客户端 fetch、clone、checkout 都指向内网地址。这种方案的焦点是 Git 对象库和引用的一致性,只要仓库能完整拉下来、能持续拿到上游更新,就算成功。第二种是制品缓存:除了代码,你还要缓存 release 产物、依赖包、容器镜像,这往往要配合 Nexus 或 Harbor 这类制品仓库一起做,光靠 Git 镜像解决不了。第三种是离线归档:你要保留每个仓库的完整历史、所有分支、所有标签、所有 LFS 对象,甚至要能随时恢复整个仓库,这需要定期做完整性校验和备份。
如果你一上来就说“我要全量镜像所有 GitHub 仓库、每分钟同步”,那运维负担会巨大,而且大部分仓库可能根本没人用。先明确核心场景,再决定技术路线,能省掉一半后续麻烦。
1.2 动手前要列出的需求清单
我在正式搭建之前,给自己列了一份清单,每一条都直接影响方案选型:
| 问题 | 可能的答案 | 影响 |
|---|---|---|
| 仓库清单怎么定? | 人工维护 / 自动抓取组织仓库 | 决定同步脚本的复杂度 |
| 需要多深的历史? | 只要最新代码 / 完整历史 | 是否允许浅克隆 |
| 仓库是否用到 LFS? | 有大量二进制 / 没有 | 决定是否必须用 Gitea、GitLab 这类支持 LFS 的平台 |
| 是否使用子模块? | 有 / 没有 | 子模块仓库也要加入同步清单 |
| 只读还是可写? | 只读镜像 / 内网还要提交代码 | 决定服务端要不要开放 receive-pack |
| 同步频率? | 30 分钟 / 2 小时 / 每天 | 影响带宽和临时存储占用 |
| 人从哪里访问? | 开发机 / 构建机 / 两者 | 决定客户端配置方式 |
这些问题看起来琐碎,但每一个都能让你在搭建过程中少做一次返工。比如我一开始没有确认 LFS 的问题,结果第一批仓库同步完之后,测试 clone 才发现大文件全丢,只能补方案。
1.3 一个反直觉的结论
搭建镜像站最大的坑往往不是“搭建”,而是“同步策略”。一次全量同步成功只是开始;后续增量同步、强制更新、LFS 对象缺失、子模块指向外网,这些才是真正让镜像“腐化”的原因。很多镜像站上线时好好的,跑了两三个月之后,团队开始抱怨“有些仓库拉不下来”“某些分支怎么不见了”,多数都是同步策略出了问题,而不是初始搭建的问题。
2. 方案选型:四种主流的仓库镜像路线
2.1 路线A:使用 GitLab CE 的 Pull Mirroring
如果团队本来就有 GitLab,这可能是最省事的方案。GitLab 自带“仓库镜像”功能,支持从远端仓库定期 Pull 同步到内部项目。
操作路径是:在 GitLab 中创建一个空项目,进入 Settings -> Repository -> Mirroring repositories,填写上游仓库地址,选择认证方式(公开仓库用 HTTPS 匿名即可,私有仓库需要配置 Access Token 或 SSH Key),然后设置同步间隔。
Pull Mirroring 有几个特点值得注意。第一,它是 GitLab 原生功能,增量同步和强制更新都由 GitLab 自己处理,不需要额外写脚本。第二,它默认会把远端引用完整拉过来,如果上游强推了历史,你可以在配置时勾选相应选项,让本地镜像跟随上游覆盖。第三,它的缺点是同步任务在 GitLab 的后台队列里跑,仓库量特别大的时候,队列可能堆积,你需要观察 Sidekiq 的状态。
如果你已经有 GitLab 实例,且仓库数量在几十个以内,这个方案足够可靠。
2.2 路线B:使用 Gitea 的镜像仓库功能
Gitea 是我个人比较喜欢的一个轻量 Git 服务,安装简单、内存占用低,而且自带仓库镜像功能。
Gitea 创建仓库时,可以选择“迁移/镜像”入口,填入 GitHub 仓库地址,勾选“镜像仓库”选项,Gitea 就会自动执行一次全量导入;之后可以在仓库设置里调整同步间隔,也可以手动触发同步。
这个方案非常适合仓库数量较少、团队又需要一个 Web 界面来查看仓库状态的场景。Gitea 本身支持启用 LFS,如果你在配置里打开了 LFS,镜像同步时也能把 LFS 对象一并处理,这是裸仓库脚本方案很难做到的一点。缺点是大规模批量管理不方便——如果要对几百个仓库统一配置镜像、统一切换同步策略,在界面上一个个点会非常痛苦。
2.3 路线C:裸仓库 + Git HTTP Backend
这是最底层、最灵活,也最“极客”的方案。思路很简单:在服务器上用git clone --mirror把上游仓库完整镜像成裸仓库,之后定时用git remote update --prune做增量同步,最后通过 Nginx 加git-http-backend对外提供只读的 Git HTTP 服务。
这个方案的优势是极致轻量,不依赖数据库、Web 服务之外的任何东西,完全可控,而且可以脚本化批处理几百上千个仓库。缺点是没有用户体系、没有 Web 管理界面,LFS 对象的托管也需要额外设计。但如果你只需要一个“纯只读代码源”,它比任何重量级平台都靠谱。
2.4 四种方案怎么选
| 方案 | 增量同步 | LFS 支持 | Web 管理 | 维护成本 | 适合场景 |
|---|---|---|---|---|---|
| GitLab Pull Mirroring | 原生支持 | 支持 | 有 | 中 | 已有 GitLab 的团队 |
| Gitea 镜像仓库 | 原生支持 | 支持 | 有 | 低 | 小团队、仓库数量少 |
| 裸仓库 + Git HTTP Backend | 脚本控制 | 需额外设计 | 无 | 低 | 批量仓库、纯只读 |
| 反向代理缓存 | 依赖缓存规则 | 难 | 无 | 高 | 不推荐作为主方案 |
我最终的选择是:仓库数量不大、需要 Web 界面查看的部分走 Gitea 镜像;大批量、纯给构建机提供代码源的仓库走裸仓库池。两边互补,既不牺牲可视性,也不牺牲批量能力。
3. 核心机制:Git 镜像同步的底层逻辑
3.1 裸仓库和普通仓库到底差在哪
要理解镜像同步,先得搞清楚裸仓库是什么。普通克隆出来的仓库,既有.git目录,也有工作区文件,你可以直接修改代码、提交、查看 diff。裸仓库则只有.git目录里的内容,没有工作区。可以简单理解成:普通仓库是“工作台 + 档案柜”,裸仓库只有“档案柜”。
git clone --bare和git clone --mirror得到的都是裸仓库,但两者有细微差别。普通 clone 默认只关心默认分支,远端跟踪引用也只会按refs/remotes/origin/*的规则存放;git clone --bare虽然复制了仓库对象,但对远端引用的覆盖并不完整。git clone --mirror则会设置remote.origin.mirror=true,并把 refspec 写成+refs/*:refs/*,这样本地镜像会完整复制上游的分支、标签、远端跟踪引用,甚至 HEAD,做到“原样副本”。
所以做镜像站,起步命令一定要用git clone --mirror而不是git clone --bare。
3.2 增量同步为什么必须用 fetch
镜像站不能每次同步都重新 clone 一次。仓库大一点,动辄几 GB,重新 clone 既浪费带宽,还容易把磁盘 I/O 打满。
正确做法是第一次全量 clone,之后使用 fetch 做增量。Git 的 fetch 会先和远端交换引用信息,再只下载本地缺失的对象。对于活跃的开源项目,每次增量可能只有几 MB 到几十 MB,速度快得多。
常规的增量同步命令是:
git -C /data/git-mirrors/example.git remote update --pruneremote update会拉取所有远端引用,--prune会删除那些在上游已经被删掉的分支和标签。因为镜像仓库配置了remote.origin.mirror=true,refspec 是+refs/*:refs/*,所以 fetch 之后本地引用会准确地反映上游状态。
3.3 引用规范和强制更新
再往深一层说,为什么镜像同步经常要用到“强制更新”?
Git 默认不允许非快进更新。比如你本地分支指向 commit A,远端分支却因为历史改写指向了 commit B,而 A 并不是 B 的祖先,这时候直接 fetch 会报non-fast-forward错误。镜像站需要跟随上游,必须允许覆盖,所以 refspec 里的+前缀就是“允许强制覆盖”的意思。
+refs/*:refs/*这个 refspec 虽然看起来简单,但它表达了一个很重要的设计理念:镜像仓库不是“你可能希望保留的工作仓库”,而是“上游仓库的忠实投影”。理解了这一点,后面处理强推、标签删除、历史改写这些问题时,思路都会清晰很多。
4. 搭建实操:两条能跑通的镜像站路线
4.1 两条路线如何选
如果团队需要一个 Web 界面,想看某个仓库最近有没有同步成功、能手动点一下“立即同步”,那优先选 Gitea 或 GitLab。如果目标是批量给构建机提供只读代码源,仓库可能上千个,没有一个统一的 Web 入口也无所谓,那裸仓库池方案最直接。
我在实际操作中把两条路线都搭了一遍,下面分别讲清楚,你可以按需取用。
4.2 路线A:Gitea 图形界面“仓库镜像”操作步骤
Gitea 安装很轻量,不管是二进制还是 Docker 都很方便。以 Docker 为例,最简化的 compose 文件大致长这样:
services: gitea: image: gitea/gitea:latest restart: always ports: - "3000:3000" volumes: - /data/gitea:/data environment: - GITEA__SERVER__ROOT_URL=https://mirror.internal.example.com - GITEA__SERVER__LFS_START_SERVER=true安装完成后,管理员在后台创建团队和账号。然后在创建仓库页面选择“迁移/镜像”,来源选择 GitHub,填入上游仓库地址。公开仓库不需要密钥,私有仓库可以配置 Access Token。
保存后 Gitea 会立刻做一次全量导入。接下来进入仓库的 Settings -> Mirror 页面,可以设置同步间隔。Gitea 支持配置默认同步频率,也支持单独为每个镜像仓库设置不同的间隔。仓库数量少时,界面操作完全够用。
实际操作中有一个小提示:Gitea 镜像功能默认只镜像代码仓库本身,子模块仓库不会自动跟着镜像。如果你发现某些仓库 clone 下来之后执行git submodule update失败,多半是子模块地址还是指向上游 GitHub 的地址,需要把子模块仓库也加进镜像清单,或者用下文 6.1 的 URL 改写方案处理。
4.3 路线B:裸仓库池 + Git HTTP Backend 的完整流程
路线B是我这次的主力方案。先看整体架构:
上游 GitHub 仓库 -> 同步脚本(clone --mirror / remote update --prune)-> 裸仓库池 /data/git-mirrors -> Nginx + git-http-backend -> 内网开发者/构建机
整个链路里,同步脚本负责维护裸仓库池,Nginx 负责把裸仓库池以 Git HTTP 协议暴露出去。客户端把 Git 地址从 GitHub 改写为内网镜像地址即可。
4.3.1 服务器与存储准备
服务器配置不需要太高,2 核 4G 内存起步就够跑 Nginx 和同步脚本。真正的瓶颈是磁盘和文件系统。
Git 仓库里有大量的小对象文件,建议使用 ext4 或 XFS,不要用 NTFS、FAT32 这类不适合大量小文件的文件系统。仓库盘容量按当前仓库总量再预留 2 倍空间比较稳妥,因为增量同步的过程中,Git 对象会先落到本地,之后再被清理或压缩,瞬时占用可能比仓库实际大小高出不少。
目录结构建议这样设计:
/data/git-mirrors/ example-org/ repo-a.git repo-b.git other-org/ lib-x.git每个裸仓库单独一个目录,后续做权限控制、磁盘统计、监控告警都清晰。
4.3.2 Nginx + git-http-backend 配置
git-http-backend是 Git 自带的 CGI 程序,Nginx 通过 fcgiwrap 或其他 CGI 方式调用它。以 Debian/Ubuntu 为例,先安装必要组件:
apt install nginx fcgiwrap git systemctl enable --now fcgiwrapNginx 配置核心部分:
location ~ ^(?<repo>.+\.git)(?<pathinfo>.*)$ { auth_basic "Mirror Read Only"; auth_basic_user_file /etc/nginx/mirror.htpasswd; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /usr/lib/git-core/git-http-backend; fastcgi_param GIT_HTTP_EXPORT_ALL ""; fastcgi_param GIT_PROJECT_ROOT /data/git-mirrors; fastcgi_param PATH_INFO $pathinfo; fastcgi_pass unix:/var/run/fcgiwrap.socket; }这里有几个细节值得说明。
第一,GIT_HTTP_EXPORT_ALL允许导出所有裸仓库,不用在每个仓库里手动创建git-daemon-export-ok文件。第二,GIT_PROJECT_ROOT指向裸仓库池根目录,这样访问https://mirror.internal.example.com/example-org/repo-a.git时,git-http-backend 会自动找到/data/git-mirrors/example-org/repo-a.git。第三,auth_basic做了一层访问认证,避免镜像站成为内网里的公开匿名源。当然,如果你的内网环境完全可信,也可以去掉认证。
默认情况下git-http-backend只处理 upload-pack 的请求,也就是 clone、fetch、pull 这些只读操作;receive-pack 的推送操作默认不开启。这意味着这个镜像站点天然是只读的,不会出现团队误把代码推到镜像站上的情况。
4.3.3 批量同步脚本
接下来是核心的同步脚本。我维护了一个仓库清单文件/etc/git-mirror/repos.txt,每行一个org/repo,支持井号注释。同步脚本读取清单,对每个仓库执行首次镜像或增量同步:
#!/usr/bin/env bash set -euo pipefail MIRROR_ROOT="/data/git-mirrors" REPO_LIST="/etc/git-mirror/repos.txt" LOG_FILE="/var/log/git-mirror/sync.log" UPSTREAM_BASE="https://github.com" mkdir -p "$MIRROR_ROOT" mkdir -p "$(dirname "$LOG_FILE")" while read -r repo; do [[ -z "$repo" || "$repo" == \#* ]] && continue mirror_dir="$MIRROR_ROOT/$repo.git" upstream_url="$UPSTREAM_BASE/$repo.git" if [[ ! -d "$mirror_dir" ]]; then echo "[$(date '+%F %T')] initial clone: $repo" git clone --mirror "$upstream_url" "$mirror_dir" else echo "[$(date '+%F %T')] incremental fetch: $repo" git -C "$mirror_dir" remote update --prune fi done < "$REPO_LIST"把这个脚本放到/usr/local/bin/sync-mirrors.sh,加执行权限,然后配置定时任务。我用的是 crontab,每两小时执行一次:
0 */2 * * * /usr/local/bin/sync-mirrors.sh >> /var/log/git-mirror/cron.log 2>&14.3.4 首次全量同步的并发控制
第一次同步大批量仓库时,最怕的是把带宽和磁盘 I/O 打满。Git 的同步是大文件居多,但仓库数量上来之后,并发数太高会导致磁盘随机读写飙升,同步速度反而变慢。
我的做法是先小规模验证三个仓库,跑通之后再用xargs控制并发度做全量:
cat /etc/git-mirror/repos.txt | xargs -P 4 -I {} /usr/local/bin/sync-one.sh {}sync-one.sh就是上面 while 循环里的单仓库逻辑,抽出来单独执行。4 个并发是比较保守的值,大多数服务器都能承受;如果带宽很宽、磁盘是 SSD,可以慢慢往上加。
5. 增量同步与一致性保障:镜像仓库的五个拦路虎
5.1 大仓库和浅克隆的诱惑
很多教程会建议:仓库太大,用--depth 1只克隆最新提交就好了。但在镜像站场景下,这基本是个坑。
浅克隆仓库没有完整历史,后续增量同步时 Git 无法基于前一次状态做增量计算,你很可能需要重新全量克隆才能拿到新提交。而且一旦某个构建任务需要检出历史版本,浅镜像就直接卡死。我的建议是:如果单个仓库超过 5GB,优先评估是不是真的需要镜像它,或者只镜像 release 分支和主要 tag;不要轻易用浅克隆来“优化”。
如果某个仓库确实非常大,首次全量同步可能会超时,可以考虑选择网络相对空闲的时段执行,或者用git clone --mirror加GIT_HTTP_LOW_SPEED_LIMIT这类环境变量来控制超时行为,但核心原则仍是“完整优先”。
5.2 LFS 对象:最容易忽略的隐形存储
这是我在路线上最想强调的一个坑。Git LFS 的工作方式是把大文件放在独立的 LFS 对象存储里,仓库本身只保存一个指针文件。执行git clone --mirror的时候,Git 默认只同步指针,不会同步真实的 LFS 对象。也就是说,镜像站看起来“完整”,但实际上大文件并不在镜像里。
要解决这个问题,裸仓库方案里可以在同步脚本中加上git lfs fetch --all origin,把 LFS 对象也拉下来。但拉下来之后还有一个问题:客户端通过git-http-backendclone 时,LFS 客户端会去请求 LFS 端点下载对象,而git-http-backend本身并不处理 LFS 协议的端点请求。这意味着你需要额外搭建一个对象存储或者 LFS 缓存服务。
所以我的结论很直接:凡是启用 LFS 的仓库,就老老实实走 Gitea 或 GitLab 这类支持 LFS 的平台镜像,裸仓库池方案只适合那些没有 LFS、纯粹代码的仓库。
5.3 子模块同步:上游引用的“套娃”问题
子模块是另一个容易被忽略的问题。主仓库通过gitlink记录了子模块仓库的 commit 引用,但子模块的内容在另一个仓库里。主仓库同步得再勤快,如果子模块仓库没有同步,客户端执行git submodule update --init时还是会去请求上游地址,同步失败。
我在实际操作中遇到过这样一个场景:某个项目通过 submodule 引用了第三方组件库,镜像主仓库后 clone 一切正常,但初始化子模块时直接报错,因为子模块地址指向上游 GitHub,内网环境无法访问。最后解决办法是把子模块仓库也加入同步清单,并让客户端统一走 URL 改写,问题才解决。
建议在梳理仓库清单时,顺便检查每个仓库的.gitmodules文件,把里面出现的子模块仓库一并纳入镜像计划。这样才不会出现“主仓库通了、子模块断了”的半通状态。
5.4 上游强推和标签改写
开源项目偶尔会强推某个分支,或者删除、重建某个 tag。镜像站如果同步得够勤,通过remote update --prune会把这些变化同步过来,因为 refspec 带了+前缀,允许强制覆盖。
客户端这边要注意:如果开发者的本地仓库长期没有 fetch,断点处的历史记录可能被改写,再次 fetch 时会看到“上游历史已变”的提示。这是镜像机制的预期行为,不是故障。如果团队的重要仓库需要防篡改,不要依赖镜像站本身,应当另做一份 git bundle 归档或者定期快照,而不是试图把镜像仓库改成“不可变存储”。
5.5 同步状态的可观测性
镜像站跑起来之后,最怕的是“悄悄坏掉”。某个仓库连续几天同步失败、磁盘剩余空间不足、某个裸仓库对象损坏,如果没有监控,这些问题可能直到团队报障才会被发现。
我写了一个每日巡检脚本,遍历所有裸仓库,统计每个仓库的引用数量和最近同步时间:
for repo in /data/git-mirrors/*/*.git; do refs=$(git -C "$repo" for-each-ref | wc -l) last_sync=$(stat -c %y "$repo/FETCH_HEAD" 2>/dev/null || echo "unknown") echo "$repo refs=$refs last_sync=$last_sync" done配合每天一次的 crontab,输出结果可以直接发到内部聊天机器人或者邮件,形成最基本的同步台账。只要“最近同步时间”没有持续更新,就说明同步链路出了问题,可以提前介入。
6. 客户端接入:让团队无感切换到镜像地址
6.1 最省事的 URL 改写:insteadOf
镜像站搭好之后,最怕的是让团队一个一个改 remote URL,既费时又容易出错。Git 自带一个很实用的配置项url.<base>.insteadOf,可以实现自动改写。
比如我们在客户端机器上执行:
git config --global url."https://mirror.internal.example.com/".insteadOf "https://github.com/"之后所有原本走https://github.com/xxx的 clone 和 fetch,都会被 Git 自动改写为https://mirror.internal.example.com/xxx,团队完全不需要改仓库里的 remote 配置。
如果只希望某个项目使用镜像,就改成--local;如果公司用 Ansible、SaltStack 这类配置管理工具,可以直接把配置写进系统级/etc/gitconfig,一次下发全部搞定。
6.2 替代方案:直接改 remote URL
仓库数量少、团队可控时,直接改 remote URL 更直观:
git remote set-url origin https://mirror.internal.example.com/example-org/repo-a.git两种做法各有优劣。insteadOf 的优势是对所有新 clone 自动生效,缺点是它会把所有 GitHub 地址都改写掉,如果团队偶尔需要直接访问上游 GitHub,反而会造成困扰。直接改 remote URL 更精准,但需要有人维护仓库清单,新增仓库时容易漏掉。
我的建议是:构建机、CI 环境尽可能用全局 insteadOf,因为它们只需要稳定地从镜像拉取代码;开发者本机可以用--local按项目切换,避免影响日常操作。
6.3 身份认证与证书
内网镜像站用 HTTPS 时,如果证书是由内部 CA 签发的,客户端会报证书验证失败。一定要在客户端安装内部 CA 证书,或者通过git config --global http.sslCAInfo /path/to/ca.crt指定证书文件。
认证方面,裸仓库方案我建议用 BasicAuth 加 HTTPS,账号由管理员统一分配。不要把密码明文写在 Git 命令里,Git 会把这个 URL 记录到配置文件中,存在泄露风险。更好的做法是用 Git 凭证助手,或者使用只读专用 token。
6.4 镜像延迟的体验问题
镜像同步是定时执行的,意味着“GitHub 上刚提交的新代码,镜像站不会立刻就有”。如果团队习惯了直接访问 GitHub,切换到镜像站之后,可能刚提交完代码,回头从镜像 fetch 却发现没有最新 commit,容易产生疑惑。
解决办法有几个:同步间隔设短一点,比如 30 分钟;仓库数量少时手动触发同步;或者在镜像站首页放一个“最近同步时间”的说明。我最后选择在内部文档里写清楚:“镜像站每 2 小时与上游同步,需要最新提交时请等待下一次同步或联系管理员手动触发。”提前说明比事后解释要舒服得多。
7. 上线后的监控与日常维护
7.1 一个可控的同步状态台账
上线第一天我还挺兴奋,但第二周就发现有一个低频仓库悄悄同步失败。原因是对应仓库的上游地址变了,原地址返回 404,脚本却因为没有失败重试机制,每次都在同一个地方报错。
后来我换了一种更稳定的方式:每天写一份同步报告,把每个仓库的同步结果、失败原因、引用数量、磁盘占用都记录下来。报告不需要很复杂,简单的文本输出就行,关键是“连续两次报告都没有更新某仓库的同步时间”时,你能及时发现。
7.2 磁盘和 inode 告警
Git 仓库里的小对象非常多,每个对象都是一个文件。这意味着除了看df -h之外,还要关注df -i的 inode 使用率。很多团队磁盘空间看起来还剩很多,但 inode 已经耗尽,仓库更新直接失败。
如果文件系统是 XFS,可以在创建时指定较大的 inode 比例;如果是 ext4,后续扩容相对麻烦。更实际的做法是定期执行du -sh /data/git-mirrors/*观察仓库增长趋势,提前规划磁盘扩容。
7.3 定期恢复演练
镜像站的价值在于“随时能把代码完整分发出去”。如果它本身是一个黑盒,没人验证过是否能从镜像站完整 clone 仓库、是否能正常执行git fsck --full,那它就是一个潜在故障点。
我建议每月做一次恢复演练:在干净的机器上从镜像站随机选两个仓库 clone,执行git fsck --full检查对象库完整性。如果仓库使用了 LFS,还要确认大文件能从镜像下载成功。别等到要用的时候才发现对象不完整,那时候就晚了。
7.4 后续扩展思路
这个框架搭完之后,其实还可以往几个方向扩展。
仓库清单可以用 GitHub API 定时抓取组织仓库列表,自动生成repos.txt,省去人工维护的麻烦;高频仓库和低频仓库可以设置不同的同步频率,比如高频仓库 30 分钟一次、低频仓库每天一次;更进一步,release 产物和容器镜像可以同步到内网制品库,配合代码镜像实现“一整条构建链路完全离线化”。
如果让我重新搭一遍,我会在第一天就把仓库清单、LFS 预案、子模块检查这些“脏活”做完,而不是边搭边补。同步策略一定是在初期想清楚,后面才会越跑越顺。镜像站不是那种“配完就能甩手”的服务,它需要持续关注、持续维护,但只要把同步台账、监控告警和恢复演练这三件事做好,它就能安安静静地给整个研发体系兜底。