从GitLab到Gitea:利用PowerShell脚本实现数百仓库的自动化迁移实战

张开发
2026/4/16 7:59:22 15 分钟阅读

分享文章

从GitLab到Gitea:利用PowerShell脚本实现数百仓库的自动化迁移实战
1. 从GitLab到Gitea为什么需要自动化迁移最近遇到一个棘手的问题公司那台跑了多年的GitLab服务器突然罢工了。这台服务器承载着几百个代码仓库包括核心业务代码、历史项目和各种工具脚本。由于硬件老化加上多次网络改造最终在一次办公室搬迁后彻底躺平——能读到硬盘数据但无法正常提供服务。手动迁移听起来简单但实际操作时会遇到几个致命问题仓库路径结构不兼容GitLab支持多层路径如group/subgroup/project.git而Gitea只支持单层结构元数据丢失直接复制.git目录会导致仓库配置不完整规模问题几百个仓库手动操作不仅耗时还容易出错这时候就需要一个自动化迁移方案。经过多次尝试我发现用PowerShell脚本配合Gitea API是最佳解决方案。整个过程就像搬家时用专业打包工具——既保证物品完好无损又能高效完成搬运。2. 抢救数据从故障GitLab提取仓库2.1 物理层数据提取当GitLab虚拟机无法正常启动时首先要做的是物理数据提取。我的操作步骤通过ESXi控制台登录故障虚拟机定位Git数据目录默认在/var/opt/gitlab/git-data使用cp -d命令备份-d参数保留软链接cp -d /var/opt/gitlab/git-data ~/gitBack遇到磁盘空间不足时我的解决方案是给虚拟机临时挂载新硬盘将数据拷贝到新硬盘通过SSH将数据拉取到本地2.2 处理VMDK磁盘镜像如果连SSH都无法使用就需要直接操作虚拟磁盘用7-zip打开VMDK文件比DiskGenius更可靠解压出git-data目录得到原始仓库结构repositories/ ├── hashed/ │ ├── 12/34/1234...567.git │ └── ... └── group/ ├── subgroup/ │ └── project.git └── ...3. 仓库结构改造PowerShell脚本实战3.1 扁平化处理路径GitLab的多级路径需要转换为Gitea兼容的单级格式。我写的PowerShell脚本$d Resolve-Path ../ $outPath $d \outGitRepos\ New-Item -ItemType Directory -Path $outPath Get-ChildItem | foreach { $base_path_name $_.Name Get-ChildItem -Path $_.Name | foreach { $path_name $base_path_name \ $_.Name $path_name2 $base_path_name _ $_.Name $new_path_name $path_name2 -replace .git, New-Item -ItemType Directory -Path $outPath$new_path_name\.git Copy-Item $path_name\* $outPath$new_path_name\.git -Recurse } }这个脚本实现了将group/subgroup/project.git转换为group_subgroup_project/.git保留所有Git对象和引用处理任意深度的嵌套路径3.2 修复仓库配置迁移后的仓库需要更新config文件。创建标准配置模板[core] bare false repositoryformatversion 0 filemode false symlinks false ignorecase true logallrefupdates true然后用脚本批量应用Get-ChildItem -Directory | foreach { $path $_ \.git Copy-Item -Force config $path }4. Gitea API自动化创建仓库4.1 准备仓库元数据从GitLab备份中提取项目信息需要数据库访问权限SELECT replace(b.path, /, _), concat({ name: ,replace(b.path, /, _),, description: , a.description, }) FROM projects a LEFT JOIN routes b ON b.source_id a.id AND source_type Project将结果保存为JSON数组用于后续API调用。4.2 批量创建仓库使用Gitea的API接口需要先配置Basic Auth$headers { Authorization Basic [base64编码的用户名:密码] Content-Type application/json } $repos Get-Content repos.json | ConvertFrom-Json foreach ($repo in $repos) { $body { name $repo.name description $repo.description private $true } | ConvertTo-Json Invoke-RestMethod -Uri http://gitea-server/api/v1/user/repos -Method Post -Headers $headers -Body $body }4.3 转移仓库到组织创建完个人仓库后统一转移到目标组织foreach ($repo in $repos) { $body { new_owner target-org } | ConvertTo-Json Invoke-RestMethod -Uri http://gitea-server/api/v1/repos/user/$($repo.name)/transfer -Method Post -Headers $headers -Body $body }5. 最终推送批量上传代码在所有仓库中执行git pushGet-ChildItem -Directory | foreach { $path $_.Name Start-Process cmd.exe -ArgumentList /k cd /d $path git remote add origin http://gitea-server/target-org/$path.git git push -u origin --all }这个命令会进入每个仓库目录添加新的远程地址推送所有分支到Gitea6. 避坑指南实战中的经验总结在完成这次迁移后我总结了几个关键注意事项磁盘空间问题临时挂载硬盘时建议用ext4格式比NTFS更稳定可以使用rsync替代cp命令支持断点续传网络不稳定时的对策对大型仓库超过1GB单独处理使用git bundle打包后再传输验证数据完整性迁移后运行git fsck检查仓库健康状态对比GitLab和Gitea的commit hash是否一致性能优化技巧并行执行API调用控制并发数对于空仓库直接跳过推送步骤使用内存盘RAM Disk处理临时文件

更多文章