ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

GitHub到GitLab迁移与同步全攻略:方案选型、实操与避坑指南

GitHub到GitLab迁移与同步全攻略:方案选型、实操与避坑指南

1. 项目背景与整体设计思路

代码托管平台的迁移这件事,做过一次的人都知道,最怕的不是"搬不过去",而是"搬过去之后对不上"。我前后经手过不下十次仓库从 GitHub 同步到 GitLab 的活儿,规模从几个人的小工具库到上百人协作的中台项目都有,踩过的坑基本能写一本小册子。这里说的"同步从 GitHub 导入到 GitLab 的项目",指的是把 GitHub 上的代码仓库(含提交历史、分支、标签、Issue、合并请求等附属数据)完整或按需迁移到 GitLab,并且在迁移之后保持两边可控的同步关系——注意,是"可控",不是"实时双向镜像",这两者的工程复杂度差着一个数量级。

这件事解决的核心痛点很具体:团队协作平台切换、内部合规要求代码托管在自建环境、开源项目需要在内网做镜像备份、或者单纯想把分散在多个平台的仓库收拢到一处统一管理。适合阅读这篇内容的人包括:负责平台迁移的运维或 DevOps 工程师、需要把个人开源项目搬家的独立开发者、以及被领导一句"把代码都迁到内网"砸中但不知道从哪下手的技术负责人。不管你是第一次做迁移还是已经迁过几个仓库,下面这些关于方案选型、历史保留、同步策略的细节,应该都能帮你少走几段弯路。

先说清楚一个基本认知:GitHub 到 GitLab 的"导入"和"同步"是两件不同的事。导入是一次性动作,把某个时间点的仓库状态搬过去;同步是持续性动作,要求源端变化能反映到目标端。很多人一开始没分清这两个概念,上来就配镜像,结果发现 Issue 和 PR 根本不过去,或者镜像仓库变成了只读,团队没法在 GitLab 上正常协作。所以整个方案设计的第一步,是先明确你的真实需求属于哪一类。

1.1 需求分层:你到底要搬什么

我把迁移需求拆成三个层次,你可以对号入座。第一层是纯代码层:只要 commit 历史、分支、标签齐全,能 clone 能 push 就行,Issue、PR、Wiki 这些附属数据不要。第二层是协作数据层:除了代码,还要把 Issue、合并请求、评论、里程碑、标签体系一起搬过去,保证团队切换平台后历史讨论不丢失。第三层是持续同步层:迁移完成后,源端和目标端还要保持某种同步关系,可能是单向镜像(GitHub 更新自动推到 GitLab),也可能是双向但人工介入的受控同步。

这三层对应的技术方案完全不同。纯代码层最简单,一条git clone --mirror加git push --mirror就能搞定,十分钟的事。协作数据层必须走 GitLab 官方提供的导入接口,因为 Issue 和 PR 的数据结构、编号映射、用户映射都需要平台层面处理,手工搬不现实。持续同步层则要在前两层基础上再加镜像配置或定时任务,而且要考虑冲突处理策略。我见过太多人把第三层的需求用第一层的方法做,结果每次源端更新都要手动重推一遍,维护成本高得离谱。

提示:在动手之前,先拿一张纸写清楚"迁移后团队要在 GitLab 上做什么"。如果答案是"日常开发全在 GitLab 进行",那 GitHub 那边就应该转为只读归档,不要搞双向同步,否则两边提交分叉后合并的痛苦远超你的想象。

1.2 方案选型的三个关键考量

选型时我主要看三个维度:数据完整性、可重复性、维护成本。数据完整性决定你搬过去的东西够不够用;可重复性决定你能不能把这套流程脚本化、批量处理几十个仓库;维护成本决定迁移完之后你还要不要天天盯着它。

GitLab 官方提供了几种导入途径:Web 界面的"Import project"支持从 GitHub 直接拉取(需要授权令牌),API 方式适合批量操作,还有gitlab-rake系列命令用于自建实例的底层导入。对于协作数据层的需求,Web 导入和 API 导入是唯二靠谱的选择,因为它们会处理 Issue、PR、评论的映射关系。纯代码层则可以用裸仓库镜像的方式,速度快、不依赖平台接口、对源端零侵入。

这里有个容易被忽略的点:GitHub 的 PR 在 GitLab 里对应的是 Merge Request,但两者的编号体系是独立的。GitLab 导入时会尽量保留原始编号,但如果目标项目里已经有 MR,编号就会冲突并重新分配。这意味着导入后你看到的 MR 编号可能和 GitHub 上不一致,历史讨论里引用的"#123"可能指向了别的东西。这个问题没有完美解法,只能在迁移前清空目标项目的 MR,或者接受编号偏移并在团队内公告。

2. 核心细节解析与实操要点

方案定下来之后,真正决定成败的是细节。我见过仓库搬过去 commit 历史断成两截的,见过大文件导致推送失败的,见过子模块路径失效的,也见过导入到一半卡住几小时没反应的。这一章把几个高频出问题的环节拆开讲,每个都附上我实际处理时的做法和判断依据。

2.1 认证方式的选择与令牌配置

不管走哪条路,认证都是第一道坎。GitHub 这边现在基本都用 Personal Access Token(PAT),密码认证早就不可用了。创建 PAT 时权限范围要选对:纯代码同步只需要repo权限;如果要导入 Issue 和 PR,GitLab 的导入器还需要读取仓库元数据的能力,通常repo加上read:org(如果涉及组织仓库)就够了。令牌有效期建议设短一点,迁移窗口期内有效即可,迁完就吊销,别留长期有效的令牌在配置里。

GitLab 这边接收推送需要的是具备api和write_repository权限的令牌,或者直接用项目访问令牌。自建 GitLab 实例如果开了双因素认证,用账号密码走 HTTP 推送会失败,必须用令牌。我一般习惯在迁移脚本里用环境变量注入令牌,而不是写死在命令里,避免令牌泄漏到 shell 历史或日志中。

# 令牌通过环境变量注入,避免出现在命令历史里 export GH_TOKEN="你的github令牌" export GL_TOKEN="你的gitlab令牌" # 验证 GitHub 令牌可用性 curl -s -H "Authorization: token $GH_TOKEN" https://api.github.com/user | head -5

注意:令牌权限宁大勿小是错的。迁移场景下权限给多了反而增加泄漏风险,按最小必要原则配置,迁完立即吊销。我吃过一次令牌权限过大且忘记吊销的亏,虽然没出事,但事后排查时一身冷汗。

2.2 提交历史与分支标签的完整保留

纯代码迁移的核心命令是裸仓库镜像。git clone --mirror会把源仓库的所有 refs(分支、标签、远程跟踪分支)原样克隆到本地裸仓库,然后git push --mirror推到目标端。这个组合能保证 commit 哈希值完全不变,历史图谱一模一样,这是判断迁移是否"无损"的硬标准。

# 裸仓库镜像克隆,保留所有 refs git clone --mirror https://github.com/用户名/仓库名.git repo.git # 进入裸仓库目录 cd repo.git # 推送到 GitLab,--mirror 保证 refs 全量同步 git push --mirror https://oauth2:$GL_TOKEN@gitlab.example.com/组名/仓库名.git

这里有个细节值得说:--mirror推送会强制覆盖目标端的 refs,包括删除目标端有而源端没有的分支。如果目标项目是新建的空仓库,这没问题;如果目标项目已经有人推过东西,--mirror会把那些东西抹掉。所以迁移前务必确认目标仓库是干净的,或者改用--all加--tags的保守推送方式,只增不删。

标签的处理也常被忽略。GitHub 上的 release 对应的是标签,--mirror会把标签一起带过去,但 release 的说明文字(release notes)不在 git 数据里,属于平台元数据,裸仓库方式搬不过去。如果 release notes 对你们重要,就得走 GitLab 的导入接口,或者手工在 GitLab 上重建 release。

2.3 大文件与仓库体积的预处理

仓库体积是迁移失败的头号原因。GitHub 对单文件有 100MB 的硬限制,但历史里累积的大文件会让整个仓库膨胀到几个 GB,推送时超时或中断的概率极高。我处理过一个仓库,历史里有个被删掉的 800MB 数据文件,虽然当前分支看不到,但它在历史里,clone 下来整个仓库 2.3GB,推了三次都断。

处理办法有两个方向。一是迁移前用git filter-repo清理历史中的大文件,这个工具比老的filter-branch快得多也安全得多,但会改写 commit 哈希,属于破坏性操作,必须在源端有完整备份的前提下做。二是调整 Git 的传输参数,增大缓冲区、降低压缩级别、分批推送,适合不能改写历史只能硬推的场景。

# 增大 post buffer,缓解大仓库推送中断 git config http.postBuffer 524288000 # 降低压缩级别,减少 CPU 开销,适合大仓库 git config core.compression 0 # 分批推送标签,避免一次性推送过多对象 git push origin --tags

实测下来,http.postBuffer调到 500MB 能解决大部分中等规模仓库的中断问题,但如果是网络本身不稳定,还得配合重试机制。我在脚本里通常加一个循环重试,失败后等几十秒再推,最多重试五次,成功率能到九成以上。

2.4 子模块与 LFS 对象的特殊处理

如果仓库用了 Git Submodule,裸仓库镜像不会自动处理子模块的内容,子模块只是记录了一个指向外部仓库的引用。迁移后如果子模块的 URL 还是指向 GitHub,而内网环境访问不了 GitHub,clone 下来子模块目录就是空的。解决办法是在迁移后批量修改.gitmodules文件里的 URL,指向 GitLab 上对应的镜像仓库,然后提交这个修改。

Git LFS 的情况类似但更麻烦。LFS 对象存储在独立的存储服务上,git clone --mirror默认只拉取 LFS 指针文件,不拉实际的大对象。要完整迁移 LFS 内容,得用git lfs fetch --all先把所有 LFS 对象拉到本地,推送时再用git lfs push --all推过去。如果仓库 LFS 用量很大,这一步会非常耗时,建议单独安排时间窗口。

特殊内容裸仓库镜像是否覆盖补充操作
普通 commit 历史是无
分支与标签是无
子模块引用是(仅引用)修改 .gitmodules 中的 URL
LFS 指针是需 fetch/push LFS 对象
Issue 与 PR否走 GitLab 导入接口
Release notes否手工重建或走导入接口
Wiki 页面否单独克隆 Wiki 仓库迁移

3. 实操过程与核心环节实现

这一章把完整流程走一遍,从迁移前的准备到迁移后的验证,每一步都给出具体命令和判断标准。我按"纯代码迁移"和"含协作数据迁移"两条线分别写,你可以根据前面的需求分层选择对应路线。

3.1 迁移前的盘点与备份

动手前先盘点。列出所有待迁移仓库,记录每个仓库的体积、分支数量、是否用 LFS、是否有子模块、Issue 和 PR 的数量级。这个盘点表后面会变成迁移进度表,也是出问题时排查的依据。仓库体积可以用 GitHub API 查,分支和标签数量在仓库页面上能直接看到。

备份这一步不能省。虽然迁移是"复制"不是"移动",源端数据不会丢,但迁移过程中如果对源端做了清理操作(比如 filter-repo),就必须先有完整备份。我的习惯是在迁移前对每个仓库做一次裸仓库克隆存到本地归档盘,既是备份也是迁移的中间产物,一举两得。

# 批量克隆组织下所有仓库到本地归档目录 # 需要先安装 gh 命令行工具并完成认证 gh repo list 组织名 --limit 1000 --json name,diskUsage \ | jq -r '.[] | .name' \ | while read repo; do git clone --mirror "https://github.com/组织名/$repo.git" "archive/$repo.git" done

这段脚本用gh列出组织下所有仓库,逐个裸克隆到archive目录。--limit 1000是防止仓库数量超过默认分页限制,实际数量按需调整。克隆完成后检查每个目录的体积,和盘点表对不上的要单独查原因。

3.2 纯代码迁移的完整执行

纯代码迁移的执行顺序是:创建目标项目、裸仓库推送、验证 refs 一致性、配置默认分支。创建目标项目可以通过 GitLab API 批量做,也可以手工建几个先试水。我建议先拿一个中等规模的仓库做试点,跑通全流程再批量操作。

# 通过 GitLab API 创建空项目 curl -X POST -H "PRIVATE-TOKEN: $GL_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"仓库名","path":"仓库名","visibility":"private"}' \ "https://gitlab.example.com/api/v4/projects"

项目建好后执行推送。推送完成后验证 refs 一致性是关键一步,方法是对比源端和目标端的 refs 列表。裸仓库里git show-ref能列出所有 refs 及其指向的 commit 哈希,两边输出排序后 diff,没有差异才算迁移完整。

# 源端 refs 列表 cd archive/仓库名.git && git show-ref | sort > /tmp/source_refs.txt # 目标端 refs 列表(重新克隆目标仓库后检查) git clone --mirror https://oauth2:$GL_TOKEN@gitlab.example.com/组名/仓库名.git /tmp/target.git cd /tmp/target.git && git show-ref | sort > /tmp/target_refs.txt # 对比差异 diff /tmp/source_refs.txt /tmp/target_refs.txt

diff 输出为空说明 refs 完全一致。如果有差异,通常是标签没推全或者某些分支被保护规则拦住了。GitLab 的默认分支保护规则会阻止直接推送,迁移时要么临时关闭保护,要么用有权限的账号推送。

3.3 含协作数据的导入流程

需要搬 Issue 和 PR 的时候,走 GitLab 的 GitHub 导入器。在 GitLab 的"新建项目"页面选择"Import project",再选 GitHub,填入 GitHub 的 PAT,然后从列表里选择要导入的仓库。导入器会自动拉取仓库代码、Issue、PR、评论、标签、里程碑,并做用户映射。

用户映射是个需要提前想清楚的问题。GitHub 上的贡献者在 GitLab 上不一定有账号,导入器会尝试按邮箱匹配,匹配不上的贡献者会显示为一个占位用户。如果团队人员基本重合,提前让所有人在 GitLab 上注册并绑定和 GitHub 一致的邮箱,能大幅提高映射成功率。匹配不上的部分,导入后可以在项目设置里手工重新指派。

导入过程是异步的,大仓库可能跑几十分钟甚至几小时。GitLab 的导入状态可以在项目设置里看到,也可以通过 API 查询。导入期间不要对源仓库做大的变更,否则可能出现数据不一致。导入完成后重点检查三样东西:Issue 数量是否和源端一致、PR 是否都转成了 MR、评论是否挂在正确的讨论串下。

提示:GitLab 导入器对 GitHub 的 rate limit 比较敏感,一次导入太多仓库容易触发限流导致部分数据缺失。批量导入时建议控制并发,一次跑三到五个,跑完检查结果再继续。

3.4 迁移后的同步关系配置

如果迁移后还需要保持同步,GitLab 提供了"仓库镜像"功能。在项目设置的"Repository"里找到"Mirroring repositories",填入源仓库的 HTTPS 地址和认证信息,选择拉取方向(Pull)或推送方向(Push)。Pull 模式是 GitLab 定期从 GitHub 拉取更新,适合 GitHub 仍是主开发端、GitLab 只做镜像的场景;Push 模式是 GitLab 把本地变更推到 GitHub,适合 GitLab 为主、GitHub 做展示的场景。

镜像的同步频率可以配置,最短几分钟一次。但要注意,镜像同步只同步 git 数据,不同步 Issue 和 PR,也不处理冲突。如果两边都有提交,Pull 模式下 GitLab 会尝试快进合并,快进不了就同步失败并在项目里报错,需要人工介入。所以镜像功能适合"一端只读"的场景,不适合真正的双向协作。

# 通过 API 配置拉取镜像 curl -X POST -H "PRIVATE-TOKEN: $GL_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "url": "https://github.com/用户名/仓库名.git", "enabled": true, "direction": "pull" }' \ "https://gitlab.example.com/api/v4/projects/项目ID/remote_mirrors"

配置完成后手动触发一次同步验证效果,然后在 GitLab 的镜像页面看最近同步时间和状态。如果状态一直是失败,多半是认证问题或者源端有 GitLab 拉不下来的 refs(比如某些特殊格式的标签)。

4. 常见问题与排查技巧实录

迁移这件事,文档里写的都是顺利路径,实际做的时候问题一个接一个。这一章把我遇到过的高频问题整理成速查表,每个都附上排查思路和解决办法,都是实打实踩出来的经验。

4.1 推送中断与超时问题排查

推送中断是最常见的问题,表现是git push跑到一半报RPC failed或early EOF。原因通常是仓库太大、网络不稳定、或者服务端有请求体大小限制。排查顺序是:先看仓库体积,超过 1GB 的基本都会遇到;再看网络,用ping和curl测到目标端的延迟和丢包;最后看服务端配置,自建 GitLab 的 Nginx 有client_max_body_size限制,默认值可能不够。

解决办法按优先级排:调大http.postBuffer、降低压缩级别、分批推送、必要时清理历史大文件。如果服务端是自建的,还要检查 Nginx 和 GitLab 的请求体限制配置。我遇到过一次怎么调客户端参数都断的情况,最后发现是反向代理的请求体限制卡在 100MB,改完配置立刻就好了。

现象可能原因排查动作解决办法
RPC failed / early EOF仓库过大或网络不稳查仓库体积、测网络延迟调大 postBuffer、分批推送
413 Request Entity Too Large服务端请求体限制查 Nginx 配置调大 client_max_body_size
推送卡住无响应认证失败或权限不足用 curl 测令牌检查令牌权限与有效期
部分标签推送失败标签被保护规则拦截查目标端保护规则临时关闭保护或换高权限账号

4.2 历史断裂与哈希不一致的定位

迁移后如果发现 commit 哈希和源端对不上,说明历史被改写了。常见原因有三个:用了git filter-repo清理过历史、推送时用了--force覆盖了部分提交、或者中间经过了某个会改写提交的工具(比如某些代码格式化钩子)。哈希不一致本身不一定是问题,但如果团队依赖 commit 哈希做追溯(比如 CI 日志里引用的哈希),就会造成混乱。

定位方法是拿源端某个已知 commit 的哈希,在目标端git cat-file -t查是否存在。存在说明历史完整,不存在说明该 commit 在迁移中丢失或被改写。如果确认是 filter-repo 导致的,那属于预期内的改写,迁移前就应该记录好新旧哈希的映射关系,方便后续追溯。

4.3 权限与保护规则的隐形拦截

GitLab 的默认分支保护规则会在迁移时悄悄拦截推送,表现是推送命令返回成功但某些分支没上去,或者报pre-receive hook declined。这个问题的隐蔽之处在于,报错信息不一定明确指向保护规则,有时候只显示一个笼统的拒绝。

排查时先看目标项目的"Protected branches"设置,确认要推的分支是否在保护列表里。如果在,要么临时取消保护,要么用 Maintainer 及以上权限的账号推送。批量迁移时我习惯在脚本里先调 API 把所有保护规则清掉,迁完再按需重建,这样能避免逐个仓库手工处理。

# 查询项目的保护分支 curl -H "PRIVATE-TOKEN: $GL_TOKEN" \ "https://gitlab.example.com/api/v4/projects/项目ID/protected_branches" # 取消某个分支的保护 curl -X DELETE -H "PRIVATE-TOKEN: $GL_TOKEN" \ "https://gitlab.example.com/api/v4/projects/项目ID/protected_branches/分支名"

4.4 导入后数据缺失的补救

走导入器迁移后,偶尔会发现 Issue 少了一些、或者某些 PR 的评论没过来。原因可能是导入过程中触发了 GitHub 的限流、或者某些特殊格式的数据导入器不支持。补救办法是先查 GitLab 的导入日志(自建实例在production.log里,SaaS 版在项目设置里能看到导入状态),确认是限流还是格式问题。

限流导致的缺失,等限流窗口过去后重新导入一次通常能补全,但要注意重复导入可能产生重复数据,最好在空项目上重导。格式问题导致的缺失,只能手工补,量大的话可以用 GitHub API 导出原始数据,再通过 GitLab API 批量创建。我处理过一个 Issue 评论丢失的案例,最后是用脚本把 GitHub 上的评论拉下来,按 Issue 编号对应关系批量 POST 到 GitLab,几百条评论跑了十几分钟补完。

4.5 镜像同步失败的典型场景

镜像配置好之后同步失败,最常见的原因是认证过期和 refs 冲突。认证过期表现为同步状态一直显示失败,手动触发也失败,重新填一次令牌就好。refs 冲突表现为同步报"non-fast-forward",说明两边都有新提交,GitLab 没法自动合并。

处理 refs 冲突要看你的同步方向。Pull 模式下,如果 GitLab 本地有 GitHub 没有的提交,同步就会卡住,解决办法是先把本地提交推到 GitHub,或者接受本地提交被覆盖(不推荐)。Push 模式反过来。我的建议是镜像场景下严格保持"一端只读",从流程上杜绝冲突,而不是等冲突了再想办法合并。

5. 批量迁移的脚本化与效率优化

单个仓库迁移手工做没问题,几十上百个仓库就必须脚本化。这一章讲怎么把前面的流程串成可重复执行的脚本,以及批量操作时的效率技巧和风险控制。

5.1 迁移脚本的结构设计

脚本我一般分成四段:读取仓库清单、逐个克隆、逐个推送、逐个验证。每段之间用文件传递状态,失败的不影响后续的,最后汇总报告。这样设计的好处是任何一段中断都能从断点续跑,不用从头再来。

仓库清单用一个文本文件维护,每行一个仓库名,简单直接。克隆和推送的日志分别写到独立文件,方便出问题时定位是哪个仓库哪一步失败。验证段对比 refs 后输出通过和失败的清单,失败的单独人工处理。

#!/bin/bash # 批量迁移脚本骨架 LIST="repos.txt" ARCHIVE="archive" LOG_DIR="logs" mkdir -p "$ARCHIVE" "$LOG_DIR" while read repo; do echo "=== 处理 $repo ===" # 克隆 if [ ! -d "$ARCHIVE/$repo.git" ]; then git clone --mirror "https://github.com/组织名/$repo.git" "$ARCHIVE/$repo.git" \ >> "$LOG_DIR/clone.log" 2>&1 || { echo "$repo clone失败" >> "$LOG_DIR/failed.txt"; continue; } fi # 推送 cd "$ARCHIVE/$repo.git" git push --mirror "https://oauth2:$GL_TOKEN@gitlab.example.com/组名/$repo.git" \ >> "../$LOG_DIR/push.log" 2>&1 || { echo "$repo push失败" >> "../$LOG_DIR/failed.txt"; cd ..; continue; } cd .. echo "$repo 完成" >> "$LOG_DIR/done.txt" done < "$LIST"

这个骨架够用但不完善,实际使用时还要加上重试逻辑、并发控制、以及推送前的目标项目创建。并发控制尤其重要,同时跑太多克隆和推送会把本地带宽和目标端都打满,反而更慢。我一般控制在同时跑三到五个。

5.2 并发与限流的平衡

批量迁移的瓶颈通常在两端:GitHub 的 API 限流和 GitLab 的写入压力。GitHub 对未认证请求限流很严,认证后宽松一些,但批量克隆几十个仓库还是可能触发。GitLab 这边主要是推送时的服务端处理压力,并发太高会导致部分推送超时。

我的经验值是并发三到五,克隆和推送分开跑,先全部克隆完再统一推送。这样克隆阶段可以稍微激进一点(并发五),推送阶段保守一点(并发三)。如果遇到限流报错,脚本里加指数退避重试,第一次等 30 秒,第二次等 60 秒,依次翻倍,最多等五分钟。

5.3 迁移进度的可视化跟踪

仓库多了之后,光看日志文件很难掌握整体进度。我习惯在脚本里维护一个状态文件,每个仓库一行,记录当前状态(待处理、克隆中、推送中、已完成、失败)和时间戳。需要看进度时用awk统计各状态数量,一目了然。

# 统计各状态仓库数量 awk '{print $2}' status.txt | sort | uniq -c

这个简单的统计在迁移几十个仓库时特别有用,能快速判断整体进度和失败比例。失败比例超过一成就要停下来查原因,不要闷头跑完再处理,否则可能一批仓库都因为同一个原因失败。

6. 迁移后的验证与团队交接

迁移完成不等于事情结束,验证和交接做不好,团队用起来照样一堆问题。这一章讲迁移后要验证哪些东西,以及怎么让团队平滑过渡到新平台。

6.1 完整性验证的检查清单

验证分三个层面:代码层、协作数据层、配置层。代码层验证 refs 一致性(前面讲过方法)、随机抽查几个历史 commit 的内容、确认默认分支正确。协作数据层验证 Issue 数量、PR 转 MR 的数量、标签体系是否完整、里程碑是否带过来。配置层验证分支保护规则、CI 配置、Webhook、部署密钥这些是否需要在 GitLab 上重建。

CI 配置是最容易被忽略的。GitHub Actions 的配置文件在 GitLab 上不生效,需要转成.gitlab-ci.yml。如果项目依赖 CI 做自动化测试和部署,迁移后 CI 不跑起来,团队会以为代码有问题。我的做法是迁移前就把 CI 配置转换好,迁移后第一时间验证流水线能跑通。

6.2 团队使用习惯的平滑过渡

平台切换对团队来说是个不大不小的冲击,clone 地址变了、PR 流程变了、通知渠道变了,都需要适应。交接时我一般做三件事:发一份简短的迁移公告,写清楚新地址、认证方式、和旧平台的差异点;在 GitLab 上把默认分支、保护规则、合并权限配好,让日常流程和原来尽量一致;保留 GitHub 仓库一段时间只读,方便大家对照查历史。

公告里最值得强调的是认证方式的变化。GitHub 用 PAT,GitLab 也用 PAT 但创建入口和权限名称不同,很多人第一次用会卡在认证上。把创建令牌的路径和需要的权限范围写清楚,能省掉大量重复答疑。

6.3 长期维护的几点经验

迁移完成后,源端仓库的处理方式要提前定好。如果确定不再回 GitHub,就把源端归档并设置为只读,避免有人误推。如果还要保持镜像,就按前面讲的配置好同步并定期检查同步状态。我个人的习惯是迁移后观察两周,确认 GitLab 上一切正常,再把 GitHub 仓库归档。

还有一点是文档和链接的更新。项目 README 里的 clone 地址、CI 徽章、文档里的仓库链接,迁移后都指向旧地址,需要批量替换。这个工作量不大但很琐碎,建议在迁移脚本里加一步自动替换,或者迁移后统一 grep 一遍处理。

最后分享一个我在多次迁移中总结的小技巧:迁移前在 GitHub 仓库的 README 顶部加一行迁移公告,写明"本仓库已迁移至新地址,此仓库转为只读归档",并保留一段时间。这样即使有人从旧链接进来,也能第一时间知道去哪找最新的代码,比事后逐个通知高效得多。这个公告本身也是一次迁移完成的标志,看到它就说明这个仓库的迁移闭环了。

返回列表