git-repo这个工具,用过大项目的人都知道,一把梭管理几十上百个Git仓库确实舒服,但它的.repo目录也是真的让人又爱又怕。爱的是有了它,整个项目的manifest、git历史、子模块引用全部归位,哪怕工作区被删光了都能一条命令拉回来;怕的是很多人压根没搞明白.repo里到底存了什么,备份代码时只打了工作区的tar包,丢了.repo之后在新机器上抓瞎。我这篇要讲一个很具体的场景:在一台Linux服务器上怎么把git-repo部署好,然后把服务器上已经存在的repo项目完整备份下来,包括.repo目录里的manifest、projects对象库、以及那一堆容易被忽略的软链接。适合做Android系统级开发、大型多仓工程、以及在构建机上维护多套代码的同学参考。
1. repo项目为什么必须单独考虑备份
1.1 先搞清楚.repo目录里到底有什么
很多人在服务器上备份代码,第一反应是tar czf打包整个工作目录,拷走就完事。如果项目只有一两个Git仓库,这个思路没问题,源码就是一切。但用repo管理的多仓项目完全不是这个逻辑,真正有价值的不是工作区里那些检出的文件,而是隐藏在.repo目录下的那堆对象库和manifest记录。
正常repo init之后,项目目录下会生成一个隐藏的.repo目录,里面大概分这么几层:manifests目录是manifest仓库的本地克隆,记录了你用的是哪个分支、拉的是哪些子项目、每个子项目锁定在什么revision上;manifest.xml是当前生效的manifest文件,可能是独立的快照文件,也可能是指向manifests目录内文件的软链接;projects目录存放所有子项目的Git裸仓库,每个子项目一个目录,注意这里不是普通的工作副本,而是带完整历史的.git裸库;比较新的repo版本还会在projects目录下生成objects和extensions目录,用来做对象共享。
换句话说,.repo目录里装的是整个项目的"元数据"加"全量对象库"。工作区文件删了,repo sync一次就能回来;但.repo没了,你连manifest拉的是什么都不知道,只能从头回忆哪个分支、哪几个仓库、什么revision,基本等于项目重建。所以备份repo项目,重点不是备份工作区,而是备份这个.repo目录。
1.2 服务器备份repo和U盘拷贝不是一回事
如果是个人笔记本上的一个repo项目,备份方案很简单,找个移动硬盘整个目录拖走。但放到服务器场景里,问题就复杂了:服务器上通常跑着多套manifest、多个分支的检出目录、不同产品线的构建产物;服务器本身可能是构建机,也可能是多人共用的开发环境;备份不仅要防磁盘故障,还要防人为误操作、防火墙损坏、以及把服务器从A机房迁移到B机房的场景。
正因为这样,服务器上的repo备份需要一套组合方案,而不是一次手工tar。我个人的做法是全量加增量搭配:全量备份定期跑一次,把整个repo项目完整打包存到备份服务器或NAS上;增量备份靠rsync或者快照工具每天同步一次,保证数据丢失最多只丢一天的工作量。如果对恢复时效要求高,还可以写一个定时cron脚本,把rsync的任务挂在凌晨低峰期跑,加一个日志输出和健康检查。
这里顺带说一句,备份repo的思路和你备份MySQL数据库、备份大模型部署目录没有本质区别。数据库有全量备份加binlog增量,repo备份同样也可以做到"全量包加每日对象同步"。把这种思路迁移过来,就不容易在关键时刻翻车。
1.3 一个真实事故:只备代码不备.repo的后果
我之前遇到过一件事,至今印象深刻。同事要在新服务器上搭一套Android构建环境,图省事,直接把旧构建机上的源码目录整个tar走了,大概40GB,拷到新机器一解压,看文件都在,觉得没问题,直接跑构建脚本。结果脚本第一步要执行repo sync拉依赖,报了各种"missing project"和"error: revision not found"。
查了半天原因,就是他tar的时候虽然把源码文件都带上了,但没带.repo目录,或者说.repo在打包时因为某种原因被排除了。新机器上的.git目录和.repo/projects里的对象库对不上,repo根本不知道每个子项目当前应该处于什么状态。最后没办法,只能重新repo init,然后把manifest里几十个仓库全部重新sync,又因为其中几个仓库在旧服务器上存了本地修改没推远端,代码直接无法恢复。那次之后,我就在团队里立了规矩:repo项目备份必须把.repo目录作为一等公民对待,打包前先du -sh .repo看看体积,备份完成后必须做一次恢复验证。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 服务器上部署git-repo的完整步骤
2.1 部署前先看三样东西:Python、Git、磁盘
git-repo本质上是一个Python脚本封装,底层调用的是系统里的Git命令。所以部署之前,不要急着去下载repo脚本,先确认三样东西:Python版本、Git版本、磁盘空间。
Python方面,新版git-repo已经全面支持Python 3,并且部分版本已经放弃Python 2,建议服务器上至少有Python 3.6以上,检查命令是python3 --version。如果服务器还在用老旧的CentOS 7,默认Python是2.7,需要额外装一个python3,用yum install -y python3就能搞定。Git版本建议2.20以上,太老的版本对部分manifest的解析会有兼容问题,git --version看一下子就行。
磁盘空间这个经常被忽略。repo sync一个大型项目,.repo目录加上工作区随便就是几十上百GB,而且repo在sync过程中会在.repo目录生成大量临时文件。我见过有同事在磁盘使用率95%的服务器上跑repo sync,跑到一半报No space left on device,然后整个.repo目录处于半损坏状态。所以部署前一定要df -h看一下服务器剩余空间,如果空间紧张,先清理构建产物或者把repo项目放到单独挂载的大分区上。
2.2 安装git-repo脚本并配置镜像源
git-repo官方推荐安装方式是直接从官方源下载repo脚本,放到/usr/local/bin目录下。但这个操作在国内网络环境下非常不稳定,经常卡住或者下载失败,我建议直接用镜像源下载,实测速度快很多,也更稳定。
安装命令如下:
bash复制sudo curl -s -o /usr/local/bin/repo https://mirrors.tuna.tsinghua.edu.cn/git/git-repo/
sudo chmod +x /usr/local/bin/repo
注意curl命令后面那个URL末尾的斜杠不要省,因为清华镜像里git-repo是一个文件,不带斜杠可能会被重定向。下载完之后,接着配置REPO_URL环境变量,这个变量告诉repo脚本去哪拉取它自己后续需要的Python库和hook。
bash复制export REPO_URL="https://mirrors.tuna.tsinghua.edu.cn/git/git-repo/"
这行配置最好写入/etc/profile.d/repo.sh或者~/.bashrc,避免每次登录都要重新export。如果服务器上的用户不止你一个,建议写到系统级的profile文件里,所有用户都能生效。还有一点,如果你是从别人的机器上拿来的repo脚本,版本可能比较旧,建议第一次使用前先执行repo selfupdate或者删除~/.repoconfig缓存再跑,避免版本不一致的问题。
2.3 验证repo工具是否可用
安装完之后不要急着init,先跑一个最简单的命令验证环境:
bash复制repo --version
正常输出会显示repo的版本号、git版本、Python版本以及操作系统信息。如果你看到类似于"fatal: unable to access"或者提示无法连接时,多半是下载repo自身代码时没走镜像源,回去检查REPO_URL环境变量有没有设置对。
再跑一个repo help看能不能正常打印帮助信息。这两步通过之后,repo工具就算是部署成功了。如果服务器同时存在多个用户,并且你希望新用户登录后就能直接用repo,记得把/usr/local/bin/repo和profile.d里的环境变量配置都设置好,不然新用户一执行repo就会得到一个command not found。
2.4 顺手把repo升级和自动补全也配了
repo脚本更新迭代挺频繁的,而且它自己维护了一套升级机制。用户执行repo命令时,如果检测到本地的repo工具版本落后于REPO_URL指向的远端版本,会提示你更新。你可以直接执行:
bash复制repo selfupdate
日常使用中,我习惯在服务器上写个小任务,定期自动跑一次repo selfupdate,保证工具版本不会落后太多。要注意的是,repo的缓存配置在~/.repoconfig目录,升级失败或缓存损坏时,果断删掉这个目录重来,比手动排查问题快得多。
如果你用的是bash或者zsh,还可以启用repo命令的自动补全,官方仓库里带了shell补全脚本。复制到/etc/bash_completion.d/目录下重新登录就行,这样打repo init、repo sync这类命令时按Tab能自动补全参数,省去不少记参数的时间。
3. 备份repo到服务器的实操方案
3.1 备份前先做信息摸底
备份一个repo项目,和备份普通代码目录最大的区别在于,repo项目需要先摸清楚当前的状态,否则你备份完之后可能根本不知道这份备份对应的是哪个分支、哪个manifest、有没有本地未推送的提交。
我建议在备份之前,先进到项目目录里做一次全面摸底,重点执行这几条命令:
bash复制# 当前所在的manifest分支,以及manifest内容
repo manifest -r -o /tmp/manifest-backup.xml
# 每个子项目当前HEAD位置,以及是否有未推送的提交
repo info
# 工作区是否有修改,v就是verbose
repo status
如果条件允许,把repo forall -c 'git remote get-url origin'执行一次,把每个子项目的remote地址输出到一个文本文件里,这个文件在重建环境时特别有用,能确认每个子项目到底是从哪个远端拉的代码。
信息摸底不只是做个记录,它还起到一个作用:确认备份时的基线状态。比如你发现某个子项目有未提交的本地修改,那你就要评估这个修改是应该先提交推送到远端,还是需要在备份时重点标注,防止恢复后找不到这部分改动。
3.2 方案一:tar全量打包,哪里都能恢复
tar是全量备份最直接的方式,优点是步骤简单、恢复友好、不依赖额外工具,缺点是打包时间比较长、产物占空间。适合做每周或者每两周一次的完整快照。
我用得比较多的命令是这样:
bash复制cd /home/user
tar czf repo-backup-$(date +%Y%m%d).tar.gz \
--exclude='*/out' \
--exclude='*/build' \
android-project
这里有一个关键点:默认tar会把你当前目录下的所有内容包括.repo目录全部打进去,所以不需要额外加包含.repo的选项。真正需要花心思的是--exclude参数。构建产物目录比如out、dist这类的,体积大而且可以重新生成,不建议打进备份包。但有些目录虽然叫build,实际是构建源码的一部分,这种就不能排除。所以排除规则一定要根据项目实际情况来,最好先在服务器上实际看一下哪些目录占了大部分空间,再决定排除谁。
tar包打好之后,强烈建议顺手生成一个md5sum校验文件:
bash复制md5sum repo-backup-$(date +%Y%m%d).tar.gz > repo-backup-$(date +%Y%m%d).tar.gz.md5
生成校验文件的目的,是为了在恢复之前判断备份包在传输过程中有没有损坏。我见过很多备份包传到NAS上之后坏了一半,但没人发现,等灾难发生恢复时才报错,那就真的晚了。
3.3 方案二:rsync增量同步,适合定时任务
全量tar包虽然稳妥,但每次把几十GB的东西打一遍再传走,既占带宽又慢。日常备份还是要靠rsync做增量同步。
rsync同步repo项目的核心命令:
bash复制rsync -av --delete --progress \
/home/user/android-project/ \
backup-server:/data/backups/android-project/
这条命令会把本地的android-project目录整个同步到备份服务器上,--delete表示删除远端有而本地没有的文件,保证远端和本地完全一致。有人说--delete太危险,怕远端被误删,但如果你明确知道远端只是备份目录,那这个参数就应该加,否则旧文件越积越多,你根本分不清哪些是当前状态、哪些是历史残留。
配合定时任务,可以做到每天自动备份。比如在/etc/crontab里加这样一行:
bash复制0 3 * * * rsync -av --delete /home/user/android-project/ backup-server:/data/backups/android-project/
需要注意,rsync跨服务器同步需要配置SSH免密,否则crontab跑的时候会卡在密码输入上。配置方法就是经典的ssh-copy-id把公钥拷到备份服务器上。还有一点,如果同步的是几百GB的目录,第一次rsync会非常慢,建议第一次先放在深夜慢慢跑,之后增量同步就快了。
3.4 方案三:rsync加--link-dest做快照式备份
如果你需要的不只是"最新状态",还想能回退到昨天、前天的版本,那就要用到rsync的--link-dest参数。这个方案可以在每次备份时为相同文件创建硬链接,而不是复制整个文件,既保留了历史快照,又不会占用太多额外磁盘空间。
备份机上先建好备份目录结构,然后这样跑:
bash复制backup_dir=/data/backups/android-project
latest_dir=$backup_dir/$(date +%Y%m%d-%H%M%S)
rsync -av --delete --link-dest=$backup_dir/current \
/home/user/android-project/ \
$latest_dir/
rm -f $backup_dir/current
ln -s $latest_dir $backup_dir/current
这个流程的逻辑是:利用--link-dest把上一次备份中没变化的文件通过硬链接方式保留下来,变化了的文件才会真正写入新快照。这样你得到的是一个又一个"看起来完整"的目录,但磁盘占用只增加实际变化的部分。对repo项目来说,.repo目录里大部分对象文件基本不会变,只有个别子项目的对象会增长,所以快照备份的空间效率非常高。
在恢复时,只需要找到对应时间点目录,整个复制出去或者直接用rsync推送回服务器就行。
3.5 从备份恢复repo的完整流程
备份做得再好,恢复流程不验证等于白做。我自己恢复repo项目时,一定会执行完整的一套流程,而不是简单解压。
第一步,准备一个干净目录,把备份包解压进去:
bash复制mkdir -p /workspace
tar xzf repo-backup-20250101.tar.gz -C /workspace
cd /workspace/android-project
第二步,检查.repo目录是否完整。ls -la .repo应该能看到manifests、projects、manifest.xml,如果这几个关键元素缺失,说明备份的时候可能没带全。第三步,执行repo init重新确认manifest状态,指向和之前一样的manifest仓库和分支:
bash复制repo init -u https://git.example.com/platform/manifest.git -b android-12.0
repo sync -c -j8
有同学会问:都已经从备份恢复了,为什么还要repo init和repo sync?原因是备份包里虽然带上了.repo/projects的完整对象库,但工作区的文件可能是旧的,而且备份之后远端可能有新的提交没同步过来。repo init加repo sync能把工作区文件重新对齐到manifest指定的版本,顺便把增量数据拉回来,这一步不能省。
最后,跑一下repo status确认工作区干净,再根据项目实际情况做一次构建或者工具链自检,确认恢复出来的环境真的能跑。
4. 常见问题与排错实录
4.1 .repo损坏或丢失时怎么抢救
这是repo项目最典型的灾难场景。如果.repo目录整个没了,工作区文件还在,抢救的方式是重新执行repo init指定同样的manifest仓库,然后repo sync。因为manifest仓库本身在远端,重新init一次工作区就能重新构建出.repo目录,git历史也会从远端拉回来。这时候之前摸底阶段输出的manifest-backup.xml就派上用场了,它能帮你确认当时用的确切manifest文件名称和分支。
如果.repo目录里projects对象库损坏了一部分,情况会麻烦一些。可以先尝试repo sync -f让它跳过失败的项目,把能恢复的先恢复,然后针对损坏的项目单独用git clone重新拉取,再放回projects目录。实际操作中有时候tar备份包在传输过程中损坏,解压时提示gzip校验错误,这种情况建议直接重新传输备份,不要抱着侥幸心理用半损坏的包硬恢复。
4.2 repo sync反复失败的几个原因
repo sync失败了,先不要慌,从三个角度排查。第一,网络问题。最常见的是SSL证书校验失败,或者是连接远端Git服务器超时,尤其是在国内网络环境拉取海外仓库时,这种报错不要太常见。解决办法是优先确认REPO_URL是否指向了可用的镜像源,再用git config --global http.sslVerify false临时跳过SSL校验,但注意这只能应急。
第二,磁盘问题。repo sync过程中会写大量临时对象,如果服务器磁盘或者inode不够,会出现诡异的中途失败。用df -h看磁盘空间,用df -i看inode是否耗尽。inode满了的情况下,磁盘空间明明还有,但任何文件都建不出来,很多人排查半天都找不到原因。
第三,manifest的问题。比如manifest里引用了某个远端分支,但这个分支被删了或者名称写错了,repo sync会报"unable to checkout"这类的错误。这种时候不要瞎改代码,直接检查manifest文件里的revision字段是否正确。
4.3 "cannot find a valid baseurl for repo"其实是yum源的问题
很多人在服务器上配完repo工具之后,继续yum install其他软件时突然看到一句"Cannot find a valid baseurl for repo: base/7/x86_64"。这时候注意了,这个报错跟git-repo没有任何关系,它说的是CentOS的yum软件源失效了。以CentOS 7为例,官方源已经停止维护,默认的mirrorlist链接全部失效,yum就会报这个错。
解决办法是换用可用的镜像源,国内用阿里云镜像是最省事的:
bash复制curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo
yum clean all
yum makecache
执行完这三条命令再yum install就正常了。顺带说一句,检查repo命令和检查yum源是完全不同的两码事,别搞混。遇到"repo"相关报错,先确认是git-repo工具还是yum源,省得绕远路。
4.4 备份可恢复性自检清单
最后分享一个我自己一直在用的备份自检清单,每条都是踩过坑之后总结的:
- 备份包打完之后,md5sum校验文件是否一并保存,并且校验数值和实际文件一致。
- 备份目录里是否明确记录了manifest分支、manifest文件名称、以及各子项目remote地址,没有记录的话恢复时全靠猜。
- 备份是否同时覆盖了.repo目录和工作区,只备工作区会让repo恢复后大量对象文件缺失。
- 增量备份每天是否稳定执行,日志里有没有累积的错误被忽略。
- 每个季度至少做一次恢复演练,不要等到服务器真的挂了才去读恢复文档。
这个清单我打印出来贴在了服务器机房的改造记录里,每当有人问"备份到底可不可靠"时,直接把清单拉出来对一遍,比嘴上说一万句都有用。
我在实际使用中发现,备份repo项目最容易被忽视的反而是那些"看起来很小"的细节:一个软链接没带上,一个manifest分支没记录,一个--delete参数没加,都可能在恢复当天造成致命问题。根据我个人经验,与其依赖一次性的大动作,不如把备份拆成"定期全量加每日增量加季度演练"这套组合拳,每一步都简单、可自动化、可验证。这样服务器上的repo项目才能做到真正的心里有底,而不是备份了个寂寞。
