
GitLab代码在不同环境之间迁移几乎是每个自己搭GitLab的团队都躲不过去的事。新应用上线要换集群、老服务器要退役、开发环境到准生产环境想共用代码基线或者干脆就是把GitLab从13.x升级到15.x——说穿了核心就一句话怎么把仓库、完整提交历史、分支标签、合并请求、CI配置、权限规则一只不差地搬到另一个地方。这篇文章把我实际做过的几种GitLab代码迁移路径、命令细节和踩坑记录完整梳理一遍。不管你是第一次做迁移还是已经迁移过几回但总在某个环节卡住照着这套思路和步骤走至少不会再犯我犯过的那些错误。1. 动手前先分清楚你要做的是哪种迁移很多人一上来就搜“gitlab代码迁移怎么做”结果看到的教程五花八门命令互相矛盾。原因很简单GitLab迁移不是一个单一操作它分好几种场景每种场景的解法完全不同。你首先得判断自己属于哪一类。1.1 同实例内的项目搬迁场景是这样的你们公司只有一个GitLab但是组织架构调整了原来了硬件组的代码要挪到基础架构组或者项目从old_group迁到new_group。这种迁移本质上是把项目的命名空间改掉代码内容、历史、成员配置都不需要变只要改个路径。操作也非常直接在GitLab界面的项目设置里找到“Transfer project”指定新的命名空间点确认即可。转移后原地址会自动加一个重定向旧链接还能用一段时间不会立刻404。纯同实例迁移不建议用后文提到的镜像方案没必要绕一大圈。除非转移的过程中提示某些条件不满足比如目标命名空间下有同名项目、或者当前项目的可见性设置不允许这么做否则这是最快的一条路。1.2 旧GitLab实例整体搬迁到新GitLab这是最典型的“代码迁移”场景。你有一个用了三五年、数据量不小的GitLab实例跑在旧机房或者老云主机上现在要整体搬到新服务器或者换版本、换部署方式比如从apt安装的改成Docker部署。这种迁移最理想的情况是“整机搬迁”也就是直接把GitLab的数据目录、配置文件、备份文件全部搬过去然后在新环境里恢复。GitLab官方支持从备份文件恢复实例备份文件里包含了所有项目、用户、组、CI配置、Runner注册信息等。只要新环境的GitLab版本和备份来源版本一致或者满足官方提示的版本约束恢复出来基本就是原样。但实际操作中有很多团队做不到版本一致。举个很常见的例子旧实例还停在GitLab 13.x新环境想直接装GitLab 15.x官方要求升级必须一步一步来不能跨越太大版本。这种时候备份恢复这条路就行不通就退而求其次做“项目级迁移”只搬代码和相关配置不搬整个系统。1.3 从其他代码平台迁到GitLab现在很多团队是从GitHub、Gitee、Bitbucket甚至SVN迁到GitLab。这种跨平台迁移的套路其实也是“项目级迁移”只是多了一步平台差异处理。GitLab官方自带导入功能支持从GitHub、Bitbucket等平台一键导入效果还不错。GitHub导入的体验最好因为GitHub的模型和GitLab最接近issues、PR、wiki都能映射过去。Bitbucket稍差一些有些字段对不上。SVN迁GitLab则不是导入能搞定的需要先把SVN仓库转成Git仓库然后走“裸仓库镜像”的方式推上去这个我在后文会展开讲。顺便说一句我见过有些人把“代码迁移”理解成“把项目文件下载下来再上传”这是完全错误的方向。Git是一个分布式版本控制系统仓库本身自带完整历史。迁移的核心是搬仓库不是搬文件。谁要是用“下载zip再上传”的方式迁代码那这个仓库的历史、分支、标签就全废了后面追查问题会非常痛苦。2. 迁移前必须做的三件事令牌、版本、命名规划方案判断完了先别急着执行任何git命令。迁移开始之前有几项准备工作每一项都能影响后面操作的成败。我按优先级排序讲。2.1 个人访问令牌整个迁移操作的“门票”在GitLab里执行迁移操作尤其是通过API、命令行或者git推送的方式大多数情况下都需要身份验证。密码虽然也能用但GitLab很多接口已经不推荐用账号密码登录了尤其是开启了2FA的账号密码根本没法用于API调用。所以第一件事是去GitLab的个人设置里创建Personal Access Token中文名叫个人访问令牌。创建路径右上角头像 → Edit profile → Access tokens。填一个名称比如“migration-token”勾选需要的权限范围api调用GitLab API的完整权限迁移脚本基本都需要read_repository读取仓库代码write_repository推送代码到仓库read_registry如果需要迁移容器镜像才需要建议直接把api勾上。它有最大的操作范围能暂时覆盖绝大多数API操作需求。等迁移完成后记得回目标GitLab把这个令牌删掉不留后患。有个别团队还保留着老的账号密码方式操作试几次发现API返回401然后跑来问是不是GitLab坏了。其实不是是新版本GitLab对令牌管理做了很多收紧比如取消了普通密码直接调用API的能力新增了SSH key和deploy token的区分。你花两分钟创建一个令牌能省掉后面排查报错的一堆时间。2.2 版本兼容性核查14.0这道坎必须心里有数你可能在IDE或GitLab API使用过程中遇到过这样的报错“login failed. GitLab versions older than 14.0 are not supported”。这是新版开发工具和插件在检测到GitLab版本时发现太旧直接拒绝连接。GitLab在14.0版本做了一个重要调整大幅收紧了API鉴权要求必须使用令牌不允许用session验证同时新的代码语言模型工具也默认不支持14.0以下的仓库。所以如果你的源GitLab还在14.0之前很多第三方工具是连不上去的迁移操作会处处受阻。我的建议是迁移之前先把旧实例的版本查清楚。方法很简单管理员账号登录后在页面左下角或者管理员区域里看版本号实在找不到就curl一下实例URL的那个元信息接口通常能看到版本信息。如果旧版本太老比如12.x、13.x迁移之前先按版本路径升一级再说不然给新环境导入的时候也会因为版本差太多被拒绝。版本差距合理范围参考这个经验值源版本目标版本是否建议直接迁移GitLab 13.xGitLab 15.x不推荐跨越太大导入导出可能报不兼容GitLab 14.xGitLab 15.x可以尝试但部分字段可能丢失GitLab 15.xGitLab 15.x最稳妥小版本升级随便走GitLab 15.xGitLab 16.x可以做项目级导入导出通常没问题还有一点新版GitLab在导入导出时对压缩包格式有严格校验。旧版本导出的压缩包新版本不一定认。所以网上有些教程教你用老版本导出再在新版本导入结果卡在版本校验上这是非常常见的问题。2.3 目标端的命名空间与可见性规划迁移最容易被忽视的是“搬到哪”。很多人先在目标GitLab里手动把空项目建好了再推代码发现要么项目路径不对要么可见性不对要么部署密钥跟丢。其实更合理的做法是先建目标命名空间group不建项目让迁移工具或推送过程自己创建项目。如果你用的是GitLab原生导入功能导入时可以选择目标命名空间导入完成后项目会自动创建。如果你用的是git push --mirror方式推代码目标端必须预先创建好空项目或者用一个能够自动创建项目的API脚本。命名空间规划还要考虑一个细节源端的group层级可能是“公司/产品线/项目”目标端如果只有一层group导入后会把层级拍扁导致一些group级别的CI变量、成员权限丢失。所以目标端的group结构最好和源端保持一致或者提前规划好映射关系。3. 最稳的保底方案裸仓库镜像搬运如果你不想依赖GitLab的导入导出机制或者源端和目标端版本差太多导致导入导出不可用那么“裸仓库镜像搬运”就是最保底、最可控的方案。这个方法的核心思想是用git clone --mirror把源仓库完整镜像到本地然后在目标端建好空仓库用git push --mirror把所有引用推上去。3.1 为什么镜像仓库比普通clone更可靠很多人会拿git clone来复制代码然后push到新仓库。普通clone确实能拿到代码和本地分支但它不会完整复制所有远程跟踪引用、标签以及合并请求中的引用尤其不会复制像refs/merge-requests/*这种GitLab内部的引用。git clone --mirror会把远程仓库的完整refspec都拉下来本质上是对远程仓库的精确复制。用完之后你再推送目标端就能完整还原源端的引用结构。这个方案对整个Git的引用模型要求很低不依赖GitLab本身的特殊导出功能所以兼容性最好哪怕源端是10.x的老GitLab只要git客户端能访问就能迁。3.2 完整操作流程三步走第一步在本地机器上执行镜像克隆。git clone --mirror git源GitLab地址:group/project.git执行完后本地会出现一个名为project.git的目录里面没有工作区文件纯粹是仓库数据。这个目录不需要修改因为它是镜像改任何东西都会破坏upstream的一致性。第二步在目标GitLab上创建空项目。如果项目少直接在目标端用HTTPS方式在网页上点新建项目选Empty project即可。但项目一多这一步就是瓶颈。推荐用API批量创建curl --header PRIVATE-TOKEN: 目标端令牌 \ --data nameprojectvisibilityprivate \ https://目标GitLab地址/api/v4/projects第三步推送镜像到目标端。cd project.git git push --mirror git目标GitLab地址:group/project.git推送完成后去目标端仓库页面看分支、标签、提交历史应该和源端完全一致。这一步的成功率非常高几乎不会出幺蛾子。3.3 大仓库与浅克隆的取舍镜像方案最怕什么最怕仓库太大。一个仓库动辄几十GB克隆和推送都会非常慢而且中途断网就前功尽弃。我的处理经验是先看单仓库大小。如果超过5GB不建议直接用git协议全量推送因为Git协议对大对象处理效率一般网络稍微不稳就会中断。有两个替代思路如果只是代码历史需要保留不带大文件附件可以考虑用git clone --filterblob:none做部分克隆减少下载体积。但要注意这样克隆出来的仓库不是标准镜像形态某些GitLab功能比如代码搜索、文件预览可能缺失需要后续触发完整数据拉取。如果项目里依赖LFS大文件镜像clone默认不会拉取LFS对象。你需要先确保源端LFS对象都还在再在目标端安装好git-lfs最后用git lfs push --all把LFS对象推上去。我踩过一次坑老仓库几百MB里面有大量设计稿图片走了LFS镜像推送完代码很顺畅一打开图片全是“对象缺失”。排查了很久才发现是LFS对象没推。现在我的做法是在镜像推送完成后每次都手动确认一遍LFS是否同步。4. GitLab原生导入导出省力但有几处深坑如果源端和目标端的GitLab都能正常访问版本差距在可接受范围内我最推荐用GitLab原生的项目导出导入功能。因为它不仅能搬代码还能带走一部分合并请求、评论、Wiki、发布记录等代码镜像方案做不到这些。4.1 项目导出能带走什么带不走什么在源GitLab项目页面Settings → General → Advanced → Export project然后系统会生成一个.tar.gz压缩包。下载下来它就是源项目的完整导出文件。导出内容大致包括这些代码仓库含全部历史、分支、标签、LFS指针合并请求及评论高版本才包含旧版本可能缺失Wiki页面项目设置、可见性、描述CI/CD流水线配置和变量里程碑、Issue标签、成员部分版本根本带不走的包括Runner这属于实例层面不是项目层面、项目级的原始构建产物、部分Webhook密钥、以及某些依赖外部集成生成的工单链接。有一点必须特别提醒不要在高版本的导出包里想办法混进大版本差异太大的旧版本还原。比如GitLab 16导出的包你硬拿到GitLab 13去导入几乎必然报格式错误。GitLab的导入导出格式虽然自称前向兼容但我实测跨三个大版本就很悬。4.2 项目导入操作简单但要看日志目标端在New Project界面选择“Import project”上传源端导出的tar.gz文件选择目标group等待导入完成。导入过程中入口处会有进度提示导入失败时会把错误信息记录在项目后台的导入历史里路径是项目设置 → General → Advanced → Import history。第一次导入失败先别急着重试去翻一下日志90%的错误原因都在里面。常见的导入失败原因目标group下已经有同名项目导入包格式损坏比如下载中途断掉导致tar.gz不完整可见性等级不匹配比如源项目是internal目标实例设置却禁止创建internal项目某些映射的成员邮箱在目标端不存在4.3 用CLI和Rake任务做批量自动化项目少的时候网页导出导入完全够用。但如果要迁移几十上百个项目一个个点界面会点到你怀疑人生。这时候就要上工具了。GitLab官方推荐用Rake任务来做实例级备份和恢复但Rake任务对项目级迁移并不友好。更通用的做法是借助API写脚本批量导出导入。GitLab项目的导出接口长这样curl --header PRIVATE-TOKEN: 源端令牌 \ --request POST \ https://源GitLab地址/api/v4/projects/项目ID/export导出完成后通过另一个接口查导出状态拿到下载链接下载tar.gz。目标端再用导入接口上传。社区里也有人喊“gitlab cli安装”其实是社区维护的GitLab命令行工具glab可以用它来简化部分操作比如查项目ID、创建项目、看合并请求。但glab目前主要覆盖的是日常开发操作对导入导出这类的管理操作支持还不够。我的建议是批量迁移脚本还是自己写用curl或Python调API最灵活glab可以作为辅助工具用别把它当成迁移的万能钥匙。5. 代码搬完后最容易丢的是CI/CD和权限配置代码推上去了仓库页面也能访问了很多人就认为迁移完成了。实际上真正的工作量才刚刚开始。CI/CD流水线、保护分支、成员权限这些配置常用迁移方案根本不会自动带过去必须手动重建。5.1 把流水线配置和变量一起搬过去先看项目根目录有没有.gitlab-ci.yml这是CI配置的核心。有的话推到目标站之后会自动被识别但前提是目标端要有可用的Runner。这里有一个团队里特别常见的坑源端自带的Runner注册信息是跟着实例走的不随项目导出。目标端不管你是新装的GitLab还是另一台机器的GitLab都不会自动继承源端的Runner。你需要重新注册Runner或者在目标端配置好共享Runner。比Runner更隐蔽的是CI变量。很多人只意识到代码里有个.gitlab-ci.yml却忘了很多关键配置放在项目设置的CI/CD Variables里比如云服务商的密钥、镜像仓库账号密码、测试环境地址。这些变量不会跟项目导出走。我的经验是迁移前在源端把所有CI/CD Variables截图留档迁移后在目标端逐条核对写回。5.2 分支保护规则与审批机制GitLab默认会保护默认分支也就是main或master只有Maintainer级别以上才能直接推送代码。很多人问“gitlab developer 可以提交代码到master吗”答案就是看默认分支有没有被保护。迁移后如果目标端默认分支的保护规则没配好开发者可能出现两种情况要么普通开发者推不上代码要么保护没生效导致任何人都能乱推。所以迁移后第一件事去项目设置里的Protected Branches里核对默认分支是否被保护允许推送的级别是不是Maintainer是否需要开启Code Owners审批或者要求Merge Request审批这些规则在项目导出时部分能跟随但跨版本或跨实例迁移时经常丢失。我在实际迁移中遇到过源端有一条“要求至少一名Maintainer审批才能合并”的规则导入后完全不见了的案例。所以别相信默认导出每个项目都要重新核对一遍。5.3 成员角色与权限矩阵的恢复还有一个老生常谈的问题项目导出的成员关系很多时候目标端无法直接导入。因为成员导入依赖用户的邮箱和账号在目标端已经存在。如果目标端是新实例用户都还没建导入时是不会自动创建用户的。这意味着你需要事先把用户在目标端建好或者通过API批量创建用户然后把各自的角色权限映射回去。给个参考映射表源端角色目标端角色说明OwnerOwner管理员通常只保留给少量人MaintainerMaintainer可管理项目、合并代码DeveloperDeveloper可push非保护分支、创建MRReporterReporter只读提IssueGuestGuest最小权限只看仪表盘批量操作可以用目标端API直接加成员curl --header PRIVATE-TOKEN: 目标端令牌 \ --request POST \ --data user_id用户IDaccess_level30 \ https://目标GitLab地址/api/v4/projects/项目ID/members注意别把Owner级别的权限误分配给Developer。这个错误我当时也犯过目标端一个项目的Developer傻乎乎拿到了Owner权限最后项目设置被别人改得乱七八糟才追回来。权限移交要“就高不就低”宁可缺一点也别给多。6. 迁移后的验证清单和问题排查实录代码迁移完、CI配好、成员加完这时还不能直接对外宣布“迁移完成”。你需要做一次系统性验证把常见问题捞一遍然后再正式切换。6.1 提交历史、标签、LFS文件的完整性校验迁移后最容易自欺欺人的是打开页面看到几个文件就觉得没问题。我的校验方法比较笨但有效在目标端clone一份仓库然后执行git log --oneline | wc -l git tag | wc -l git branch -r | wc -l git lfs ls-files | wc -l把得到的数据和源端对比。数字完全一致才算代码层面过关。对比的时候特别注意标签数量很多镜像方案漏推tags因为有些Git命令推引用时默认不会带tags。还有一个细节迁移后开发者本地仓库里的remote地址还是源端的。你需要通知团队执行git remote set-url origin git目标GitLab地址:group/project.git git fetch origin如果开发者习惯用IDE操作比如IDEA里直接改Git remote URL或者VSCode里重新“克隆存储库”填写新地址效果也一样。6.2 高频报错与排查速查表整理一张我在迁移过程中遇到的报错速查表按出现频率排序错误现象原因处理思路推送时401 Unauthorized令牌过期或权限不足回目标端重新生成Personal Access Token确认勾选api和write_repository导入时提示版本不支持源和目标版本差距过大按版本约束先中间升级或者改用镜像推送方案clone时报LFS对象缺失使用镜像clone时未同步LFS安装git-lfs用git lfs fetch --all拉全对象再推导入失败日志提示项目可见性冲突目标实例的可见性设置不允许修改目标实例的可见性限制或者先改为私有再导入迁移完毕流水线不跑Runner未注册或CI变量丢失重新注册Runner逐项核对CI/CD Variables登录IDE提示版本太老源端GitLab低于14.0先升级到14.0以上再考虑其他工具接入网页打开项目404转移项目后旧地址未重定向检查项目转移设置确认开启重定向或者等待缓存刷新第三个LFS问题最常见。很多人的仓库看着几十MBclone下来只有几MB就是因为LFS对象没跟着走。这种情况推送后再执行一次LFS全量推送基本能解决。6.3 切换收尾的几个动作验证通过后别急着删掉源实例。我见过有人在迁移完成当天就销毁旧服务器结果三个月后发现一条历史流水线的构建日志只在旧库里有。更稳妥的做法是新环境运行稳定至少一周以上确认没有遗漏需求再做数据清理。正式切换时有几个容易忽略的收尾动作更新开发文档和README里的仓库地址通知团队统一执行git remote set-url避免一部分人还在推旧地址检查有没有定时脚本依赖GitLab API地址比如CI的部署脚本里写死的webhook地址关闭旧项目的访问权限或者把旧实例置为只读模式防止切换期间两边数据不一致用Docker部署的GitLab还要多检查一步容器内/etc/gitlab/gitlab.rb里配置的external_url是否改成新域名/IP很多人忘了改这个导致生成的项目链接全是旧地址扫码访问全部404。整个迁移流程走完我最深的体会是代码本身反而是迁移中最容易的部分git协议足够健壮clone和push就能解决。真正的难点在“围绕代码的一切”——权限模型、CI变量、Runner、分支保护、Webhook、成员关系。这些配置类的东西分散在GitLab各个角落没有一条命令能一键带走。所以做迁移之前务必要先花半天时间列一个配置盘点清单一项一项核对而不是拍脑袋直接开迁。这样能省下的不只是几天的排查时间还有后续几个月陆续涌现的“为什么流水线不跑了”“为什么我合并不了代码”之类的人肉答疑。