Public/Invoke-TfvcMigration.ps1
|
function Invoke-TfvcMigration { <# .SYNOPSIS Orchestrates the full TFVC-to-GitHub migration pipeline. .DESCRIPTION Runs Export, Replay, Verify, and Report steps in sequence. Supports dry-run mode, selective step skipping, resume from checkpoints, and optional push to GitHub. .PARAMETER ConfigPath Path to the migration config.json file. .PARAMETER DryRun Export only - shows a summary of what would be migrated without replaying. .PARAMETER SkipExport Skip the export step (use existing changesets.json). .PARAMETER SkipReplay Skip the replay step. .PARAMETER SkipVerify Skip the verification step. .PARAMETER SkipReport Skip the audit report generation step. .PARAMETER Push Push to GitHub after replay completes. .PARAMETER Resume Resume export/replay from the last checkpoint. .EXAMPLE Invoke-TfvcMigration -ConfigPath ./config.json .EXAMPLE Invoke-TfvcMigration -ConfigPath ./config.json -DryRun .EXAMPLE Invoke-TfvcMigration -ConfigPath ./config.json -Push #> [CmdletBinding()] param( [string]$ConfigPath = './config.json', [switch]$DryRun, [switch]$SkipExport, [switch]$SkipReplay, [switch]$SkipVerify, [switch]$SkipReport, [switch]$Push, [switch]$Resume ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $Version = if ($MyInvocation.MyCommand.Module -and $MyInvocation.MyCommand.Module.Version) { $MyInvocation.MyCommand.Module.Version.ToString() } else { '0.0.0' } # --- Banner --- Write-Host '' Write-Host ' =======================================================╗' -ForegroundColor Cyan Write-Host ' | TFVC -> GitHub Migration Tool v'$Version' |' -ForegroundColor Cyan Write-Host ' =======================================================╝' -ForegroundColor Cyan Write-Host '' # --- Validate config --- $resolvedConfig = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($ConfigPath) if (-not (Test-Path $resolvedConfig)) { Write-Host " [x] Config file not found: $resolvedConfig" -ForegroundColor Red Write-Host ' Run New-TfvcMigrationConfig first, or supply -ConfigPath.' -ForegroundColor Yellow throw "Config file not found: $resolvedConfig" } # --- Load and display config summary --- $config = Get-Content $resolvedConfig -Raw | ConvertFrom-Json Write-Host ' -- Configuration --' -ForegroundColor White Write-Host " Server : $($config.adoServerUrl)" -ForegroundColor Gray Write-Host " Collection : $($config.collection)" -ForegroundColor Gray Write-Host " Project : $($config.project)" -ForegroundColor Gray Write-Host " Mappings : $($config.sourceMappings.Count) source path(s)" -ForegroundColor Gray foreach ($m in $config.sourceMappings) { $dest = if ($m.destinationPath) { $m.destinationPath } else { '(root)' } Write-Host " $($m.tfvcPath) -> $dest" -ForegroundColor DarkGray } Write-Host " Git Remote : $($config.gitRemoteUrl)" -ForegroundColor Gray Write-Host " Output Dir : $($config.outputDir)" -ForegroundColor Gray Write-Host " PAT : ****" -ForegroundColor Gray Write-Host '' # --- Mode summary --- if ($DryRun) { Write-Host ' MODE: Dry Run (export only, no git operations)' -ForegroundColor Yellow; Write-Host '' } if ($Resume) { Write-Host ' MODE: Resume from checkpoint' -ForegroundColor Yellow; Write-Host '' } # --- Step runner --- $timer = [System.Diagnostics.Stopwatch]::StartNew() $stepResults = [ordered]@{} function Invoke-Step { param( [int]$Number, [string]$Name, [string]$Command, [string[]]$ExtraArgs = @() ) Write-Host " --------------------------------------------" -ForegroundColor DarkGray Write-Host " Step $Number : $Name" -ForegroundColor White Write-Host " --------------------------------------------" -ForegroundColor DarkGray if (-not (Get-Command $Command -ErrorAction SilentlyContinue)) { Write-Host " [x] Command not found: $Command" -ForegroundColor Red $stepResults[$Name] = 'NOT FOUND' return $false } $stepTimer = [System.Diagnostics.Stopwatch]::StartNew() try { $splat = @{ ConfigPath = $resolvedConfig } if ($ExtraArgs -contains '-Resume') { $splat.Resume = $true } if ($ExtraArgs -contains '-Push') { $splat.Push = $true } & $Command @splat $stepTimer.Stop() $stepResults[$Name] = "OK ($([math]::Round($stepTimer.Elapsed.TotalSeconds, 1))s)" Write-Host " [+] $Name completed in $([math]::Round($stepTimer.Elapsed.TotalSeconds, 1))s" -ForegroundColor Green Write-Host '' return $true } catch { $stepTimer.Stop() $stepResults[$Name] = "FAILED: $($_.Exception.Message)" Write-Host " [x] $Name failed: $($_.Exception.Message)" -ForegroundColor Red Write-Host '' return $false } } # --- Execute pipeline --- $failed = $false # Step 1: Export if (-not $SkipExport -and -not $failed) { $exportArgs = @() if ($Resume) { $exportArgs += '-Resume' } $ok = Invoke-Step -Number 1 -Name 'Export' -Command 'Export-TfvcChangeset' -ExtraArgs $exportArgs if (-not $ok) { $failed = $true } } elseif ($SkipExport) { Write-Host ' Step 1: Export - SKIPPED' -ForegroundColor DarkYellow $stepResults['Export'] = 'SKIPPED' Write-Host '' } # Dry-run stops here if ($DryRun -and -not $failed) { $outputDir = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($config.outputDir) $changesetsFile = Join-Path $outputDir 'changesets.json' Write-Host ' -- Dry Run Summary --' -ForegroundColor Yellow if (Test-Path $changesetsFile) { $csData = Get-Content $changesetsFile -Raw | ConvertFrom-Json $csList = $csData.changesets $count = $csData.totalChangesets Write-Host " Exported $count changeset(s) to: $changesetsFile" -ForegroundColor Gray if ($count -gt 0) { $first = ($csList | Select-Object -First 1).changesetId $last = ($csList | Select-Object -Last 1).changesetId Write-Host " Changeset range: C$first - C$last" -ForegroundColor Gray } } else { Write-Host " Export output not found at $changesetsFile" -ForegroundColor Yellow } Write-Host ' No git replay was performed (dry-run mode).' -ForegroundColor Yellow Write-Host '' $stepResults['Replay'] = 'SKIPPED (dry-run)' $stepResults['Verify'] = 'SKIPPED (dry-run)' $stepResults['Report'] = 'SKIPPED (dry-run)' } # Step 2: Replay if (-not $DryRun -and -not $SkipReplay -and -not $failed) { $replayArgs = @() if ($Resume) { $replayArgs += '-Resume' } if ($Push) { $replayArgs += '-Push' } $ok = Invoke-Step -Number 2 -Name 'Replay' -Command 'Invoke-TfvcReplay' -ExtraArgs $replayArgs if (-not $ok) { $failed = $true } } elseif (-not $DryRun -and $SkipReplay) { Write-Host ' Step 2: Replay - SKIPPED' -ForegroundColor DarkYellow $stepResults['Replay'] = 'SKIPPED' Write-Host '' } # Step 3: Verify if (-not $DryRun -and -not $SkipVerify -and -not $failed) { $ok = Invoke-Step -Number 3 -Name 'Verify' -Command 'Test-TfvcMigration' if (-not $ok) { $failed = $true } } elseif (-not $DryRun -and $SkipVerify) { Write-Host ' Step 3: Verify - SKIPPED' -ForegroundColor DarkYellow $stepResults['Verify'] = 'SKIPPED' Write-Host '' } # Step 4: Report if (-not $DryRun -and -not $SkipReport -and -not $failed) { $ok = Invoke-Step -Number 4 -Name 'Report' -Command 'New-TfvcMigrationReport' if (-not $ok) { $failed = $true } } elseif (-not $DryRun -and $SkipReport) { Write-Host ' Step 4: Report - SKIPPED' -ForegroundColor DarkYellow $stepResults['Report'] = 'SKIPPED' Write-Host '' } # --- Final summary --- $timer.Stop() $elapsed = $timer.Elapsed $elapsedStr = '{0:hh\:mm\:ss}' -f $elapsed Write-Host ' ==============================================' -ForegroundColor Cyan Write-Host ' Migration Pipeline Summary' -ForegroundColor Cyan Write-Host ' ==============================================' -ForegroundColor Cyan Write-Host '' foreach ($kv in $stepResults.GetEnumerator()) { $color = if ($kv.Value -like 'OK*') { 'Green' } elseif ($kv.Value -like 'SKIP*') { 'DarkYellow' } elseif ($kv.Value -like 'NOT FOUND*') { 'Yellow' } else { 'Red' } Write-Host " $($kv.Key.PadRight(12)) : $($kv.Value)" -ForegroundColor $color } Write-Host '' Write-Host " Total elapsed : $elapsedStr" -ForegroundColor Gray $outputDir = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($config.outputDir) $reportPath = Join-Path $outputDir 'audit-report.html' if (Test-Path $reportPath) { Write-Host " Audit report : $reportPath" -ForegroundColor Gray } Write-Host '' if ($failed) { Write-Host ' RESULT: FAILED - see errors above.' -ForegroundColor Red throw 'Migration pipeline failed - see errors above.' } else { Write-Host ' RESULT: SUCCESS' -ForegroundColor Green } Write-Host '' } |