1. 引言
Azure DevOps(简称 ADO)是一个功能强大的软件开发管理平台。但在实际工作中,我们有时需要将一个项目从一个组织迁移到另一个组织。迁移的原因可能包括公司合并、组织结构调整、合规性要求或团队重组等。
⚠️ 但 Azure DevOps 并不直接支持一键迁移整个项目的功能,因此我们必须通过一些替代方法,确保仓库、工作项、流水线等资源能完整迁移。
本文将介绍几种常见的迁移方式,帮助你根据项目规模、数据复杂度和具体需求选择最合适的迁移策略。
2. 完整 Git 仓库迁移(镜像克隆法)
✅ 适用场景:项目规模较小,可接受手动操作。
该方法需要手动在目标组织中重新创建代码仓库、流水线和工作项,适用于数据量不大、结构不复杂的项目。
迁移 Git 仓库是关键步骤之一,我们可以使用 git clone --mirror
来克隆源仓库,然后推送到目标组织的新仓库中:
git clone --mirror https://dev.azure.com/{源组织}/{项目}/_git/{仓库}
cd {仓库}
然后创建目标仓库,并执行推送操作:
git push --mirror https://dev.azure.com/{目标组织}/{项目}/_git/{仓库}
⚠️ 该方式会迁移所有分支、标签等 Git 元数据,适合需要保留完整提交历史的场景。
3. 简单 Git 仓库迁移(断开重连法)
✅ 适用场景:项目仅包含 Git 仓库,没有使用看板、用户故事、任务或流水线。
这是一种更轻量级的迁移方式,适用于仅需迁移代码仓库的场景。操作流程如下:
- 克隆源仓库:
git clone https://dev.azure.com/{源组织}/{项目}/_git/{仓库}
cd {仓库}
- 移除远程配置:
git remote rm origin
- 创建目标仓库并关联远程:
git remote add origin https://dev.azure.com/{目标组织}/{项目}/_git/{仓库}
git push -u origin --all
⚠️ 该方法不会迁移工作项、看板、构建流水线等数据,仅适用于代码迁移。
4. 使用 Azure DevOps 迁移工具
✅ 适用场景:中大型项目,需要迁移工作项、构建定义等复杂数据。
对于数据量较大且结构复杂的项目,可以使用开源工具 Azure DevOps Migration Tools。
该工具支持“整体迁移”,包括工作项、构建定义、测试计划等资源。安装方式如下(Windows 环境):
winget install nkdAgility.AzureDevOpsMigrationTools
初始化配置文件:
devopsmigration init --options Basic
生成的 configuration.json
文件中可以配置源和目标组织信息、认证方式、过滤规则等。例如:
{
"MigrationTools": {
"Endpoints": {
"Source": {
"EndpointType": "TfsTeamProjectEndpoint",
"Collection": "https://dev.azure.com/nkdagility-preview/",
"Project": "migrationSource1",
"Authentication": {
"AuthenticationMode": "AccessToken",
"AccessToken": "jkashdjksahsjkfghsjkdaghvisdhuisvhladvnb"
}
},
"Target": {
"EndpointType": "TfsTeamProjectEndpoint",
"Collection": "https://dev.azure.com/nkdagility-preview/",
"Project": "migrationTest5",
"Authentication": {
"AuthenticationMode": "AccessToken",
"AccessToken": "lkasjioryislaniuhfhklasnhfklahlvlsdvnls"
}
}
},
"Processors": [
{
"ProcessorType": "TfsWorkItemMigrationProcessor",
"Enabled": true,
"WIQLQuery": "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan') ORDER BY [System.ChangedDate] desc"
}
]
}
}
迁移执行命令:
devopsmigration execute --config .\configuration.json
⚠️ 该工具虽功能强大,但不支持选择性迁移或结构转换,迁移前建议先做测试。
5. 使用 Azure DevOps REST API
✅ 适用场景:需要高度控制、自动化迁移大量数据。
通过 REST API 可以实现更灵活的迁移流程,适用于需要自动化脚本控制的场景。
下面是一个 PowerShell 脚本示例,演示如何通过 REST API 获取仓库、创建新仓库并迁移数据:
5.1 认证函数:Get-AuthHeader
function Get-AuthHeader {
param (
[string]$token
)
return [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$($token)"))
}
5.2 获取仓库:Get-Repositories
function Get-Repositories {
param (
[string]$organization,
[string]$project,
[string]$authHeader
)
$repoUrl = "https://dev.azure.com/$organization/$project/_apis/git/repositories/?api-version=7.1-preview.1"
return Invoke-RestMethod -Uri $repoUrl -Method Get -Headers @{Authorization = "Basic $authHeader"}
}
5.3 启用禁用仓库:Enable-Repository
function Enable-Repository {
param (
[string]$organization,
[string]$project,
[string]$repoId,
[string]$authHeader
)
$updateRepoUrl = "https://dev.azure.com/$organization/$project/_apis/git/repositories/$repoId?api-version=7.1-preview.1"
$updateRepoBody = @{ isDisabled = $false } | ConvertTo-Json
try {
Invoke-RestMethod -Uri $updateRepoUrl -Method Patch -Body $updateRepoBody -Headers @{Authorization = "Basic $authHeader"} -ContentType "application/json"
Write-Host "Repository '$repoId' has been enabled."
} catch {
Write-Host "Failed to enable repository '$repoId': $_"
}
}
5.4 判断仓库是否存在:Test-RepositoryExists
function Test-RepositoryExists {
param (
[string]$organization,
[string]$project,
[string]$repoName,
[string]$authHeader
)
$reposResponse = Get-Repositories -organization $organization -project $project -authHeader $authHeader
$repositoriesList = $reposResponse.value
return ($repositoriesList | Where-Object { $_.name -eq $repoName }).Count -ne 0
}
5.5 删除源仓库:Delete-Repository
function Delete-Repository {
param (
[string]$organization,
[string]$project,
[string]$repoId,
[string]$authHeader
)
$deleteRepoUrl = "https://dev.azure.com/$organization/$project/_apis/git/repositories/$repoId?api-version=7.1-preview.1"
try {
Invoke-RestMethod -Uri $deleteRepoUrl -Method Delete -Headers @{Authorization = "Basic $authHeader"}
Write-Host "Repository '$repoId' has been deleted from '$project'."
} catch {
Write-Host "Failed to delete repository '$repoId': $_"
}
}
5.6 迁移仓库函数:Move-DeprecatedRepository
function Move-DeprecatedRepository {
param (
[string]$organization,
[string]$projectFrom,
[string]$projectTo,
[string]$repoId,
[string]$repoName,
[string]$repoSshUrl,
[string]$authHeader
)
try {
$repoToUrl = "https://dev.azure.com/$organization/$projectTo/_apis/git/repositories?api-version=7.1-preview.1"
$repoToBody = @{ name = $repoName } | ConvertTo-Json
$repoToResponse = Invoke-RestMethod -Uri $repoToUrl -Method Post -Body $repoToBody -Headers @{Authorization = "Basic $authHeader"} -ContentType "application/json"
$newRepoUrl = $repoToResponse.sshUrl
Write-Host "Repository creation successful for '$repoName'."
# Clone, Add Remote, and Push
$localClonePath = "$scriptLocation\$repoName"
git clone --mirror $repoSshUrl $localClonePath
Set-Location -Path $localClonePath
git remote add new-origin $newRepoUrl
git push new-origin --all
git push new-origin --tags
# Clean up
Set-Location -Path ..
Remove-Item -Recurse -Force $localClonePath
Write-Host "Repository '$repoName' moved successfully."
# Delete original repository
Delete-Repository -organization $organization -project $projectFrom -repoId $repoId -authHeader $authHeader
return $true
} catch {
Write-Host "Error moving repository '$repoName': $_"
return $false
}
}
5.7 主执行流程
try {
$authHeader = Get-AuthHeader -token $personalAccessToken
$FromRepoResponse = Get-Repositories -organization $organizationName -project $projectNameFrom -authHeader $authHeader
$repositoriesList = $FromRepoResponse.value
# Enable disabled repositories
foreach ($repo in $repositoriesList | Where-Object { $_.IsDisabled -eq $true }) {
Enable-Repository -organization $organizationName -project $projectNameFrom -repoId $repo.id -authHeader $authHeader
}
# Move deprecated repositories
$failedRepositories = @()
foreach ($repo in $repositoriesList | Where-Object { $_.name -like "*Deprecated*" }) {
if (-not (Move-DeprecatedRepository -organization $organizationName -projectFrom $projectNameFrom -projectTo $projectNameTo -repoId $repo.id -repoName $repo.name -repoSshUrl $repo.sshUrl -authHeader $authHeader)) {
$failedRepositories += $repo.name
}
}
# Display failures
if ($failedRepositories.Count -gt 0) {
Write-Host "`nRepositories that failed to move:" $failedRepositories
}
} catch {
Write-Host "An error occurred: $_"
}
✅ 该脚本结构清晰、可扩展性强,适合大规模仓库迁移任务。
6. 总结
Azure DevOps 的项目迁移不是一键操作,但通过本文介绍的几种方式,你可以根据项目复杂度选择合适的迁移策略:
方法 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
手动镜像克隆 | 小型项目,仅需代码迁移 | 简单直接 | 无法迁移工作项、流水线 |
断开重连法 | 仅需 Git 仓库迁移 | 操作简单 | 数据完整性差 |
开源工具迁移 | 中大型项目,需完整迁移 | 支持工作项、构建等迁移 | 不支持结构转换 |
REST API 脚本 | 高度定制化、自动化 | 灵活、可扩展 | 实现复杂 |
无论选择哪种方式,迁移前务必进行测试,确保工作项、构建、测试计划等资源完整无误,避免对业务造成影响。
完整脚本和工具配置可在 GitHub 上获取:GitHub 链接