
折腾GitLab代码迁移这事儿我少说也干了七八年了。最常见的开场白往往是同事丢过来一句话“新环境GitLab搭好了把代码挪过去。”这话听着简单真操作起来从远程地址切来切去到几十个仓库批量搬运再到CI/CD变量、合并请求、成员权限这些看不见的元数据任何一个环节出问题都能让人折腾一晚上。这篇我就专门聊聊“针对GitLab进行不同环境代码迁移”的完整思路和实操路径适合正在给项目换测试环境、上线新GitLab服务器或者从老实例全量搬运代码的团队参考。1. 动手前先拆解需求这几种“代码迁移”不是一回事1.1 迁移目标决定了你的技术方案我把实际工作中遇到的GitLab迁移需求归成三类很多人一上来就选方法选错就完蛋。第一类是换环境、换服务器。比如老GitLab跑在一台快报废的机器上新机器搭好了GitLab要把仓库、用户、组、CI变量整体搬过去。这类需求追求的是“完整替代”新环境要能无缝接棒。第二类是多环境并存。开发、测试、生产各搞一套GitLab实例开发环境代码要定期同步到测试环境甚至生产环境。这类需求追求的不是一次性搬家而是持续同步。第三类最容易被忽略是单仓库的本地项目上传。很多人把本地IDEA项目推到新GitLab上发现分支、标签、历史全乱本质上是没搞清远端引用和仓库结构的关系。三类需求的技术选型完全不同第一类适合项目导入/导出或者镜像迁移第二类适合配置推送镜像仓库第三类改一下origin远程地址再push就够了。我见过最惨的情况是有人拿处理第三类需求的方式去干第一类的活结果仓库是推上去了Issues、Wiki、CI变量全丢最后花了两天手工补。1.2 按范围拆解单仓库、多仓库、整组迁移迁移范围也是选型的关键变量。只迁一个仓库手工操作完全没问题迁几十个仓库还一个个在网页上点“导出”“导入”手会点抽筋这时候必须脚本化要迁移整个Group不光仓库还有子组结构、成员角色、CI/CD变量、Webhook这一层最好直接用GitLab提供的Group级导出API脚本只做补充。我的建议是动手前先列一张迁移清单把每个环境里有哪些Group、哪些Project、哪些成员、哪些CI/CD全局变量全部列出来。听起来很繁琐但这是整个迁移工程里性价比最高的一步。清单列完方案几乎自己就浮出来了。2. 迁移前的准备版本兼容、令牌与环境变量2.1 版本兼容性很多登录失败的根本原因GitLab版本差异是迁移过程中最容易踩的暗坑。GitLab 14.0之前的旧实例在IDE插件、CLI工具和部分API调用上会直接出现“不支持”的报错。网上搜“idea login failed. gitlab versions older than 14.0 are not supported. log in”这种报错的人特别多本质就是新版工具默认用新API去探测旧版本服务版本握手失败。迁移实操里版本问题还会以另一种方式卡住你导出包的兼容性。GitLab官方设计上默认只保证相近大版本之间能互导你把一个13.x实例的导出包丢到16.x的新实例里经常出现任务卡在导入队列或者直接失败。这里没有太多捷径如果源实例实在太老最稳妥的办法是先把源实例升级到14以上再做导出迁移。升级本身有风险但总比迁移到一半发现导入不兼容要强。我个人的排查顺序是先看源和目标实例各自的版本再决定导出方式假如源实例在14以下直接放弃导入/导出功能改用git push --mirror硬迁移假如两边都是14以上再放心走API和UI导入导出。2.2 个人访问令牌的正确配置方式无论是走API脚本还是用glab CLI个人访问令牌PAT都是绕不开的凭证。创建PAT的地方在GitLab右上角头像 → Preferences → Access Tokens。需要注意的是不同GitLab版本对这个入口的UI表述不同有的是“Access Tokens”有的叫“Personal Access Tokens”。令牌的scope选择要遵循最小权限原则。只做仓库迁移给read_repository和write_repository就够了要用API查询项目列表和触发导出就必须加上api作用域如果涉及读成员列表、改项目配置一般用api加read_api组合。网上有人图省事直接勾“admin”权限这种令牌一旦泄露等于把整个GitLab实例交出去了强烈不建议。另外提醒一句令牌只在创建时显示一次页面刷新后就没了。我习惯把令牌明文先粘贴到本地临时文件再用完后立刻吊销重建避免明文长期留在文档里。# 验证令牌是否可用 curl --header PRIVATE-TOKEN: 你的令牌 https://gitlab.example.com/api/v4/version如果返回带version字段的JSON说明令牌有效如果返回401先检查令牌是否被吊销再检查用户账号是否被锁定。2.3 环境配置差异盘点地址、端口、CI变量迁移代码只是表面真正决定“迁移后能不能跑起来”的往往是这些环境差异外部URL差异老环境是http://10.0.0.5新环境是https://gitlab.example.com项目里的绝对路径、克隆地址、文档里的链接全都会变。端口差异Docker方式部署的GitLab默认监听80/443在Ubuntu上改端口跑得很痛苦经常遇到端口被占用。这时候要改gitlab.rb里的external_url和nginx[listen_port]。CI/CD变量这是最容易被忽略的。项目导出包不会带走Runner注册信息也不会自动重建CI/CD变量。很多团队迁移完代码一跑Pipeline全是红的一查才发现DEPLOY_TOKEN、SERVER_HOST这些变量一个都没配。我整理过一个配置盘点表迁移前照着填一遍基本不会漏配置项源环境目标环境迁移方式GitLab版本14.516.8直接导入/导出External URLhttp://10.0.0.5https://git.example.com改URLSSH端口222222改克隆地址CI/CD变量12个无手动或脚本重建Runner3个无重新注册Group成员5个组/40人待建脚本同步Webhook8个无手动重建这张表的核心价值在于把“代码迁移”从单纯的git操作扩展成了完整的“环境迁移”。3. 实操四条迁移路径全流程拆解3.1 路径一改origin远程地址 git push --mirror这个方案最适合单仓库迁移或者源GitLab版本太老、导入导出功能不兼容的场景。思路很简单把原来的仓库做一次裸克隆bare clone拿到它所有的分支和标签然后把本地裸仓库当成中间介质推送到目标的空白项目里。# 1. 克隆源仓库的完整引用bare模式 git clone --bare gitsource.gitlab.example:group/project.git cd project.git # 2. 查看当前的远程地址 git remote -v # 3. 在目标GitLab新建一个空白项目然后把镜像推到目标 git push --mirror gittarget.gitlab.example:group/project.git # 4. 在本地原仓库里切换origin地址 cd /path/to/your/working/project git remote set-url origin gittarget.gitlab.example:group/project.git git fetch origin git branch -vv这里我特意解释一下--mirror和普通的git push --all的区别。--all只推所有分支标签还得单独推--mirror是把本地裸仓库里的全部引用映射到远端包括分支、标签、远程跟踪分支推送完以后目标仓库的refs和源一模一样。代价是它会删除目标上本地没有的引用所以目标项目必须是空白项目。如果往一个有其他分支的存量项目里跑--mirror那些分支会消失。我踩过这个坑当时想把两个仓库合并到一个新项目里临时起了个非空项目当目标一push原项目里的release分支直接被删了还好有备份吓得够呛。3.2 路径二配置Push Mirror实现环境间持续同步如果需求是“开发环境的代码要周期性同步到测试环境”或者让两个GitLab实例保持镜像关系git push --mirror那种一次性操作的方案就不合适了得用GitLab自带的**镜像仓库Mirror**功能。具体操作路径项目 → Settings → Repository → Mirroring repositories点击“Add new mirror”。Repository URL填目标仓库地址比如gittarget.gitlab.example:group/project.git认证方式填SSH密钥或者密码勾选“Only push protected branches”可以限制只同步受保护分支点“Mirror repository”按钮GitLab会立刻执行一次同步这里有个关键参数镜像方向。GitLab支持Push和Pull两种方向。Push Mirror是把当前项目推送到远端Pull Mirror是从远端拉取代码到当前项目。别搞反了很多人以为填了远端地址就是双向同步实际上GitLab不支持同一项目双向互推会冲突。用Push Mirror做环境同步真实体验是开发环境提交后去目标环境的项目页面手动点一次“更新镜像”或者等GitLab按你设置的间隔自动同步。适合从大到小的单向发布链路比如“开发环境单向镜像到测试环境”不适合需要两边互改代码的协作场景。3.3 路径三项目导入/导出保留Issues、Wiki和合并请求这是换服务器、整体搬迁时的首选方案。GitLab项目导出会把仓库、Wiki、Issues、合并请求、CI配置等打包到一个.tar.gz文件里再在目标环境通过导入功能恢复元数据保存最完整。UI操作流程很简单源环境打开项目 → Settings → General → Export project点“Export project”系统异步打包完成后页面出现“Download export”按钮。超大项目可能要等几分钟甚至更久耐心点别重复点击。API方式更可控适合脚本化# 触发导出任务 curl --request POST \ --header PRIVATE-TOKEN: 源环境令牌 \ https://source.gitlab.example/api/v4/projects/项目ID/export # 检查导出状态status为finished时再下载 curl --header PRIVATE-TOKEN: 源环境令牌 \ https://source.gitlab.example/api/v4/projects/项目ID/export # 下载导出包 curl --request GET \ --header PRIVATE-TOKEN: 源环境令牌 \ https://source.gitlab.example/api/v4/projects/项目ID/export/download \ --output project_export.tar.gz导入到目标环境同样有UI和API两种方式。curl --request POST \ --header PRIVATE-TOKEN: 目标环境令牌 \ --form pathgroup/project \ --form fileproject_export.tar.gz \ https://target.gitlab.example/api/v4/projects/import导入完成以后目标项目会重新计算所有内部ID成员角色和原始指派的权限名会保留但Runner和CI/CD变量不会跟过来。有人问“GitLab导出能不能保留容器镜像仓库里的镜像”答案是不能镜像仓库要单独用docker pull/push的方式搬运。3.4 路径四批量迁移用脚本和glab CLI单项目用UI没问题几十上百个项目还用UI就是灾难。这时候必须上脚本。GitLab API的分页参数是per_page加page但手写循环很啰嗦我一般直接用GitLab官方维护的CLI工具glab。glab的安装很简单Mac上brew install glabWindows上winget install GitLab.GLabLinux发行版也有对应的包管理器安装方式。装完以后# 登录会引导你输入实例地址和令牌 glab auth login # 不带分页参数列出当前用户有权限的项目 glab api projects?per_page100 --paginate # 查看项目详情拿到project_id等信息 glab api projects/项目名或ID有了glab批量迁移脚本的骨架就出来了先列出源环境所有项目ID再循环调用导出API下载导出包上传到目标环境最后在目标环境重新配置CI/CD变量。网上也有团队用“影刀”这类RPA工具做代码迁移自动化比如通过界面自动化点导出、下载、导入按钮。说实话如果目标环境有完整API我不建议用RPA原因是脚本十行能搞定的事没必要引入一套重工具但有一种场景RPA确实有价值那就是源环境是老旧版本、API功能不全只能依赖网页按钮操作时。这种情况下RPA能代替人工点击但稳定性依然依赖界面元素变化我仍是那句老话能走API就走APIRPA是最后的兜底方案。3.5 迁移中的专有依赖Runner、变量、部署密钥代码迁移完成后很多人以为万事大吉一跑CI全是红叉才想起Runner没弄。Runner迁移有两条路把源环境的Runner配置拿到目标环境重新注册适合共享Runner。给项目配置新的专属Runner适合项目绑定了特定编译缓存或K8s的Runner。CI/CD变量和部署密钥就更繁琐了。GitLab不支持把变量从一个实例直接复制到另一个实例只能手工或脚本重建。glab有相关命令我强烈建议用命令批量操作而不是网页点# 查看项目已有变量列表 glab variable list -R group/project # 创建或更新变量 glab variable set DEPLOY_HOST value -R group/project # 设置受保护变量 glab variable set PROD_TOKEN value -R group/project --protected注意--protected这个参数GitLab里受保护变量只在受保护分支的Pipeline中生效。很多人创建变量时没注意这个开关迁移后变量一直不生效排查半天才发现目标环境的分支保护规则跟源环境不一样。4. 迁移过程里的高频问题与排错实录4.1 登录/认证失败版本老与令牌失效“IDEA login failed. GitLab versions older than 14.0 are not supported. Log in via Git”这类报错我见的频率很高。原因是IDEA自带的GitLab集成插件走的是新版REST API老实例版本不满足要求插件拒绝接管认证。解决办法有两个在IDEA里选“通过Git登录”Log in via Git让插件走系统git的凭据管理器用HTTP凭据甚至SSH密钥通道完成认证。升级源实例这是根治办法。很多团队老GitLab上积压了一堆高危漏洞升一次级等于顺带做了安全加固。“Login failed. Check API token or GitLab version”这种报错则多半是PAT有问题。排查顺序令牌是否过期或撤销、scope是否包含api、账号是否被锁、目标实例地址是否以/api/v4结尾。有时你会发现令牌在源环境有效在目标环境完全无效因为两个实例的账号体系不同需要为目标环境单独创建令牌。4.2 端口、URL和访问不通的问题用Docker部署GitLab时大部分人图省事按默认端口跑结果宿主机80端口被其他服务占了GitLab启动不了。Ubuntu下修改端口的标准姿势是找到/etc/gitlab/gitlab.rb改两处external_url http://gitlab.example.com:8080 nginx[listen_port] 8080然后执行gitlab-ctl reconfigure和gitlab-ctl restart。改完之后注意一个坑项目克隆URL会自动带端口老仓库里的origin如果不改还会指向旧地址迁移后第一件事就是批量检查remote地址。SSH端口也容易出问题。GitLab默认SSH端口22如果新环境的22被占用改成2222那么克隆地址要写成ssh://gitgitlab.example.com:2222/group/project.git或者改用户~/.ssh/config否则SSH密钥明明配好了git clone还是连不上。4.3 大仓库、LFS对象与导出超时仓库太大导出任务经常排队卡死。GitLab导出设计上只打包仓库本身LFS对象默认不包含在内这是个特别隐蔽的点你导出的tar.gz里没有LFS对象导入完成后代码能拉下来但LFS文件全部变成指针文件工程根本编译不过。解决方法是迁移前先做一次全量LFS拉取再单独推送到目标实例cd project_directory git lfs fetch --all git lfs push --all gittarget.gitlab.example:group/project.git如果LFS对象很多这个操作会拉很久建议放到深夜定时任务里跑。大仓库的另一种处理方案是用git clone --bare配合git push --mirror因为这种方式会把所有对象都推过去包括LFS文件前提是已经fetch下来。GitLab导出接口对超大仓库也有超时限制超过一定体积的建议直接放弃导入/导出路径。4.4 迁移后的验证清单别急着庆祝我见过太多迁移完发现分支对不上、历史丢一段的案例。所以每次我做完迁移都会按下面这张清单逐项核验检查项验证命令/方法分支数量一致git ls-remote --heads origin标签数量一致git ls-remote --tags origin当前HEAD指针一致比较源和目标仓库的git log -1历史提交完整git log --oneline --all合并请求已迁移目标环境项目页合并请求列表核对成员权限正确设置 → 成员界面核对角色CI/CD变量齐全glab variable list -R group/projectRunner能跑通在目标环境跑一个最小Pipeline清单跑完才算真正迁移完成。尤其是分支和标签数量我每次都用脚本同时跑源和目标两边的git ls-remote然后diff对比结果一眼看出问题。5. 我的实操心得与几个小建议做了这么多次迁移每次都会遇到新的意外但有几点心得体会是共通的。第一迁移不要一次搬完所有环境。先把一个非核心项目完整走一遍流程从导出、导入、变量配置到Pipeline跑通验证整套方案可行再开始批量操作。这个“试点项目”能帮你把所有隐藏问题提前暴露比如导出超时、版本不兼容、变量缺失总比批量搬完再一个个救火强。第二回滚预案必须在动手前就想好。用git push --mirror迁移到空白项目源实例不会受任何影响出问题删掉目标项目重来就行但如果是用Push Mirror做持续同步一旦目标环境被错误地配置成反向拉取可能会有代码覆盖风险。所以我的习惯是迁移期间把目标环境设成只读模式等同步稳定后再放开写权限。迁移过程中尽量不要在源环境做大规模分支清理和标签重排不然同步过去后两边refs对不上白白增加排查成本。第三善用导出前的一句备份命令。无论采用哪种迁移方式我建议都先给源项目做一次裸备份git clone --bare gitsource.gitlab.example:group/project.git /tmp/project_backup.git tar czf /tmp/project_backup.tar.gz /tmp/project_backup.git这个备份体积小、速度快却能让你在迁移翻车时拥有一个绝对的“后悔药”。特别是导入/导出方式对超大仓库不稳定有裸备份在手随时可以切换到git push --mirror这条备选路径。GitLab代码迁移这件事说到底是“代码仓库移动”和“环境配置重建”的组合拳。只要把版本兼容、令牌权限、CI/CD变量和迁移验证这四个核心点抓牢不管面对的是单仓库迁到新服务器还是多环境持续同步的复杂链路都能心里有底。希望这篇实战经验能帮你少走几个弯路把时间花在真正有价值的事情上。