Private/Wizard/Invoke-CgrRepositoryCopyWizard.ps1
|
function Invoke-CgrRepositoryCopyWizard { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $HostName, [Parameter(Mandatory)] [scriptblock] $ExecutionGuard ) $cancelResult = { [pscustomobject] @{ PSTypeName = 'CopyGitHubRepo.WizardResult' Status = 'Cancelled' MutatedGitHub = $false } } Write-CgrWizardMessage -Message 'Copy GitHub Repository' -Style Heading Write-CgrWizardMessage -Message 'Discovering repositories and checking GitHub CLI access...' -Style Hint $repositories = @(Get-GitHubRepository -HostName $HostName) $sourceRepository = $null $destinationRepository = $null $contentMode = 'Snapshot' $commitMessage = 'Initial repository commit' $destinationVisibility = $null $settingsBehavior = 'Restore' $archiveRepositoryName = $null $sameNameConfirmation = $null $existingDestinationArchiveName = $null $existingDestinationConfirmation = $null $step = 0 while ($true) { switch ($step) { 0 { $result = Select-CgrWizardRepository -Repositories $repositories -CurrentValue $sourceRepository -AllowCancel if ($result.Action -eq 'Cancel') { return & $cancelResult } $previousSource = $sourceRepository $sourceRepository = $result.Value if ($null -eq $sourceRepository) { continue } if ($null -eq $previousSource -or $previousSource.FullName -ne $sourceRepository.FullName) { $destinationVisibility = $null $archiveRepositoryName = $null $sameNameConfirmation = $null $existingDestinationArchiveName = $null $existingDestinationConfirmation = $null } $step = 1 } 1 { $result = Resolve-CgrWizardDestinationRepository -Repositories $repositories -CurrentValue $destinationRepository -AllowBack -AllowCancel if ($result.Action -eq 'Cancel') { return & $cancelResult } if ($result.Action -eq 'Back') { $step = 0; continue } if ($destinationRepository -ne $result.Value) { $destinationRepository = [string] $result.Value $archiveRepositoryName = $null $sameNameConfirmation = $null $existingDestinationArchiveName = $null $existingDestinationConfirmation = $null } if ($destinationRepository -ne $sourceRepository.FullName -and (Test-CgrGitHubRepositoryExistence -Repository $destinationRepository -HostName $HostName)) { Write-CgrWizardMessage -Message "Destination '$destinationRepository' already exists." -Status Warning $conflictResult = Read-CgrWizardChoice ` -Title 'Existing destination' ` -Choices @('Choose another destination', 'Archive and replace existing destination', 'Cancel') ` -DefaultValue 'Choose another destination' ` -HelpTopic ExistingDestination ` -AllowBack ` -AllowCancel if ($conflictResult.Action -eq 'Cancel' -or $conflictResult.Value -eq 'Cancel') { return & $cancelResult } if ($conflictResult.Action -eq 'Back') { $step = 0; continue } if ($conflictResult.Value -eq 'Choose another destination') { $destinationRepository = $null $existingDestinationArchiveName = $null $existingDestinationConfirmation = $null continue } if ([string]::IsNullOrWhiteSpace($existingDestinationArchiveName)) { $existingDestinationArchiveName = Get-CgrDefaultArchiveRepositoryName -Repository $destinationRepository } while ($true) { $archiveResult = Read-CgrWizardRepositoryName -Kind Archive -CurrentValue $existingDestinationArchiveName -AllowBack -AllowCancel if ($archiveResult.Action -eq 'Cancel') { return & $cancelResult } if ($archiveResult.Action -eq 'Back') { $existingDestinationArchiveName = $null $existingDestinationConfirmation = $null break } $candidateArchiveName = [string] $archiveResult.Value $destinationOwner = $destinationRepository.Split('/', 2)[0] $candidateArchive = "$destinationOwner/$candidateArchiveName" if (Test-CgrGitHubRepositoryExistence -Repository $candidateArchive -HostName $HostName) { Write-CgrWizardMessage -Message "Archive repository '$candidateArchive' already exists. Choose another archive name." -Status Warning $existingDestinationArchiveName = $candidateArchiveName continue } if ($existingDestinationArchiveName -ne $candidateArchiveName) { $existingDestinationConfirmation = $null } $existingDestinationArchiveName = $candidateArchiveName break } if ([string]::IsNullOrWhiteSpace($existingDestinationArchiveName)) { continue } } else { $existingDestinationArchiveName = $null $existingDestinationConfirmation = $null } $step = 2 } 2 { $result = Read-CgrWizardChoice ` -Title 'Content mode' ` -Choices @('Snapshot', 'FullHistory') ` -DefaultValue Snapshot ` -CurrentValue $contentMode ` -HelpTopic ContentMode ` -AllowBack ` -AllowCancel if ($result.Action -eq 'Cancel') { return & $cancelResult } if ($result.Action -eq 'Back') { $step = 1; continue } $contentMode = [string] $result.Value $step = 3 } 3 { $visibilityChoices = @($sourceRepository.Visibility, 'public', 'private', 'internal') | Select-Object -Unique $result = Read-CgrWizardChoice ` -Title 'Destination visibility' ` -Choices $visibilityChoices ` -DefaultValue $sourceRepository.Visibility ` -CurrentValue $destinationVisibility ` -HelpTopic DestinationVisibility ` -AllowBack ` -AllowCancel if ($result.Action -eq 'Cancel') { return & $cancelResult } if ($result.Action -eq 'Back') { $step = 2; continue } $destinationVisibility = [string] $result.Value $step = 4 } 4 { $result = Read-CgrWizardChoice ` -Title 'Supported repository settings' ` -Choices @('Restore', 'Skip') ` -DefaultValue Restore ` -CurrentValue $settingsBehavior ` -HelpTopic SupportedSettings ` -AllowBack ` -AllowCancel if ($result.Action -eq 'Cancel') { return & $cancelResult } if ($result.Action -eq 'Back') { $step = 3; continue } $settingsBehavior = [string] $result.Value $step = if ($destinationRepository -eq $sourceRepository.FullName) { 5 } else { 6 } } 5 { $archiveResult = Read-CgrWizardRepositoryName -Kind Archive -CurrentValue $archiveRepositoryName -AllowBack -AllowCancel if ($archiveResult.Action -eq 'Cancel') { return & $cancelResult } if ($archiveResult.Action -eq 'Back') { $step = 4; continue } if ($archiveRepositoryName -ne $archiveResult.Value) { $archiveRepositoryName = [string] $archiveResult.Value $sameNameConfirmation = $null } $expectedConfirmation = "SOURCE=$($sourceRepository.FullName);ARCHIVE=$($sourceRepository.Owner)/$archiveRepositoryName;REPLACEMENT=$destinationRepository" while ($true) { Write-CgrWizardMessage -Message 'Same-name replacement preserves the current repository as an archive before creating the replacement.' -Status Warning Write-CgrWizardMessage -Message 'Exact confirmation is required and cannot be bypassed.' -Style Hint Write-CgrWizardMessage -Message 'Required text:' -Style Hint Write-CgrWizardMessage -Message $expectedConfirmation -Style Value if (-not [string]::IsNullOrWhiteSpace($sameNameConfirmation)) { Write-CgrWizardMessage -Message 'A valid confirmation is already stored. Press Enter to reuse it.' -Style Hint } $inputText = Read-CgrWizardInput ` -Prompt 'Exact confirmation' ` -DefaultValue $sameNameConfirmation ` -AllowHelp ` -AllowBack ` -AllowCancel $navigation = Resolve-CgrWizardNavigationInput ` -InputText $inputText ` -AllowBack ` -AllowNext:(-not [string]::IsNullOrWhiteSpace($sameNameConfirmation)) ` -AllowCancel ` -AllowHelp if ($null -ne $navigation) { if ($navigation.Action -eq 'Help') { Show-CgrWizardHelp -Topic ExactConfirmation; continue } if ($navigation.Action -eq 'Cancel') { return & $cancelResult } if ($navigation.Action -eq 'Back') { $step = 4; break } if ($navigation.Action -eq 'Next') { $step = 6; break } } if ($inputText -ceq $expectedConfirmation) { $sameNameConfirmation = $inputText $step = 6 break } Write-CgrWizardMessage -Message 'The confirmation did not match exactly. Retry, go Back, or Cancel.' -Status Warning } } 6 { if ($contentMode -eq 'Snapshot') { $commitMessageResult = Read-CgrWizardTextValue ` -Title 'Snapshot commit message' ` -DefaultValue 'Initial repository commit' ` -CurrentValue $commitMessage ` -HelpTopic SnapshotCommitMessage ` -AllowBack ` -AllowCancel if ($commitMessageResult.Action -eq 'Cancel') { return & $cancelResult } if ($commitMessageResult.Action -eq 'Back') { $step = if ($destinationRepository -eq $sourceRepository.FullName) { 5 } else { 4 } continue } $commitMessage = [string] $commitMessageResult.Value } $planParameters = @{ SourceRepository = $sourceRepository.FullName DestinationRepository = $destinationRepository ContentMode = $contentMode HostName = $HostName PlanOnly = $true } if ($contentMode -eq 'Snapshot') { $planParameters.CommitMessage = $commitMessage } if ($destinationVisibility -ne $sourceRepository.Visibility) { $planParameters.DestinationVisibility = $destinationVisibility } if ($settingsBehavior -eq 'Skip') { $planParameters.SkipSettings = $true } if ($destinationRepository -eq $sourceRepository.FullName) { $planParameters.ArchiveRepositoryName = $archiveRepositoryName } elseif (-not [string]::IsNullOrWhiteSpace($existingDestinationArchiveName)) { $planParameters.ExistingDestinationArchiveName = $existingDestinationArchiveName } $plan = Copy-GitHubRepository @planParameters Write-CgrWizardMessage Write-CgrWizardMessage -Message 'Repository copy plan' -Style Heading Write-CgrWizardMessage -Message (" Source: {0}" -f $plan.SourceRepository) -Style Value Write-CgrWizardMessage -Message (" Destination: {0}" -f $plan.DestinationRepository) -Style Value Write-CgrWizardMessage -Message (" Content mode: {0}" -f $plan.ContentMode) if ($plan.ContentMode -eq 'Snapshot') { Write-CgrWizardMessage -Message ' Snapshot publishes the approved default-branch content as one new root commit without prior Git history.' -Style Hint Write-CgrWizardMessage -Message (" Approved source commit: {0}" -f $plan.SourceState.CommitSha) -Style Hint Write-CgrWizardMessage -Message (" Commit message: {0}" -f $plan.CommitMessage) -Style Hint $historicalRecords = Get-CgrObjectProperty -InputObject $plan.SourceState -Name 'HistoricalRecords' if ($historicalRecords) { $tagCount = [int] (Get-CgrObjectProperty -InputObject $historicalRecords -Name 'TagCount') $releaseCount = [int] (Get-CgrObjectProperty -InputObject $historicalRecords -Name 'ReleaseCount') Write-CgrWizardMessage -Message (" Git tags: {0} (not copied)" -f $tagCount) -Style Hint Write-CgrWizardMessage -Message (" GitHub Releases: {0} (not copied)" -f $releaseCount) -Style Hint if ($plan.Mode -eq 'SameNameReplacement' -and ($tagCount -gt 0 -or $releaseCount -gt 0)) { Write-CgrWizardMessage -Message 'Existing tags and GitHub Releases remain with the archived original repository; they are not recreated on the clean replacement.' -Status Warning Write-CgrWizardMessage -Message 'Create the new release tag and GitHub Release only after the clean replacement has completed and been verified.' -Style Hint } } } else { Write-CgrWizardMessage -Message ' FullHistory preserves the approved branches, tags, commits, and reachable Git LFS objects.' -Style Hint Write-CgrWizardMessage -Message (" Approved refs: {0}; reachable commits: {1}" -f @($plan.SourceState.Refs).Count, $plan.SourceState.ReachableCommitCount) -Style Hint } Write-CgrWizardMessage -Message (" Visibility: {0}" -f $plan.DestinationVisibility) if (-not [string]::IsNullOrWhiteSpace([string] $plan.ArchiveRepository)) { Write-CgrWizardMessage -Message (" Archive: {0}" -f $plan.ArchiveRepository) -Style Value } Write-CgrWizardMessage -Message ' Planned steps:' -Style Heading foreach ($plannedStep in @($plan.Steps)) { Write-CgrWizardMessage -Message (" - {0}" -f $plannedStep.Description) } $confirmation = Read-CgrWizardChoice ` -Title 'Repository copy plan' ` -Choices @('Execute', 'Cancel') ` -DefaultValue Cancel ` -HelpTopic MigrationPlan ` -AllowBack ` -AllowCancel if ($confirmation.Action -eq 'Cancel' -or $confirmation.Value -eq 'Cancel') { return & $cancelResult } if ($confirmation.Action -eq 'Back') { $step = if ($destinationRepository -eq $sourceRepository.FullName) { 5 } else { 4 } continue } if ($confirmation.Value -ne 'Execute') { continue } if ($plan.Mode -eq 'ExistingDestinationReplacement') { $expectedConfirmation = "DESTINATION=$($plan.DestinationRepository);ARCHIVE=$($plan.ArchiveRepository);REPLACEMENT=$($plan.DestinationRepository)" while ($true) { Write-CgrWizardMessage -Message 'The existing destination will be preserved as an archive before a fresh replacement is created.' -Status Warning Write-CgrWizardMessage -Message 'Exact confirmation is required and cannot be bypassed.' -Style Hint Write-CgrWizardMessage -Message 'Required text:' -Style Hint Write-CgrWizardMessage -Message $expectedConfirmation -Style Value $inputText = Read-CgrWizardInput -Prompt 'Exact confirmation' -AllowHelp -AllowCancel $navigation = Resolve-CgrWizardNavigationInput -InputText $inputText -AllowCancel -AllowHelp if ($null -ne $navigation) { if ($navigation.Action -eq 'Help') { Show-CgrWizardHelp -Topic ExactConfirmation; continue } if ($navigation.Action -eq 'Cancel') { return & $cancelResult } } if ($inputText -ceq $expectedConfirmation) { $existingDestinationConfirmation = $inputText break } Write-CgrWizardMessage -Message 'The confirmation did not match exactly. Retry or Cancel.' -Status Warning } } if (-not (& $ExecutionGuard $destinationRepository)) { return $plan } Write-CgrWizardMessage -Message 'Executing the reviewed repository copy plan...' -Status Info try { $executionResult = Invoke-CgrApprovedMigrationPlan ` -Plan $plan ` -SourceRepository $sourceRepository ` -SameNameConfirmation $sameNameConfirmation ` -ExistingDestinationConfirmation $existingDestinationConfirmation ` -HostName $HostName } catch { $errorId = ([string] $_.FullyQualifiedErrorId).Split(',', 2)[0] if ($errorId -eq 'SourceStateChangedSincePlanning') { Write-CgrWizardMessage -Message $_.Exception.Message -Status Warning Write-CgrWizardMessage -Message 'A new plan will be created for review. No GitHub mutation was performed from the stale plan.' -Style Hint $sameNameConfirmation = $null $existingDestinationConfirmation = $null $step = 6 continue } throw } Write-CgrWizardCompletionSummary -Result $executionResult return $executionResult } } } } |