Public/Test-GitHubRepositoryMigration.ps1

function Test-GitHubRepositoryMigration {
    <#
    .SYNOPSIS
    Verifies a GitHub repository migration.

    .DESCRIPTION
    Performs read-only verification of a completed repository migration. Plain Snapshot
    verification compares the source and destination default-branch Git trees and confirms
    the destination has the expected single-root-commit history shape. Snapshot migrations
    that preserved GitHub Releases are verified from the immutable migration plan reviewed
    before execution: generated checkpoint order and parentage, checkpoint tree equivalence,
    selected release-tag targets, reviewed source HEAD state, recreated GitHub Release
    metadata/assets, and Latest designation are read independently from the destination.

    FullHistory verification compares ordinary branch and tag targets, reachable commit
    counts, branch-tip trees, the default branch, and reachable Git LFS object availability.
    FullHistory release verification retains its existing live source-selection behavior.

    Snapshot -IncludeReleases requires -ApprovedPlan so verification cannot rerun live release
    selection, filtering, or topology discovery and silently redefine the expected state.
    The approved plan can be taken from the Plan property returned by Copy-GitHubRepository.
    Verification never repairs or mutates either repository.

    .PARAMETER SourceRepository
    Specifies the source repository as owner/name. For Snapshot -IncludeReleases this name is
    retained for result identity while immutable approved evidence defines expected state.

    .PARAMETER DestinationRepository
    Specifies the destination repository as owner/name. This is the repository whose migrated
    content and releases are independently read and verified.

    .PARAMETER ContentMode
    Selects the verification contract. Snapshot is the default. FullHistory preserves the
    existing branch/tag/history/LFS verification contract.

    .PARAMETER IncludeReleases
    Adds GitHub Release verification. FullHistory uses the existing live source-selection
    contract. Snapshot requires -ApprovedPlan and verifies exactly its reviewed selection.

    .PARAMETER ReleaseTag
    Includes only source GitHub Releases whose tag names match one or more PowerShell wildcard
    patterns for FullHistory verification. Snapshot approved-plan verification does not accept
    live release filters because the reviewed plan is authoritative.

    .PARAMETER ReleaseExcludeTag
    Excludes source GitHub Releases whose tag names match one or more PowerShell wildcard
    patterns for FullHistory verification.

    .PARAMETER IncludePrerelease
    Includes source prereleases in FullHistory release verification.

    .PARAMETER IncludeDraftReleases
    Includes source draft releases in FullHistory release verification.

    .PARAMETER ReleaseCount
    Limits FullHistory release verification to the newest N selected source releases.

    .PARAMETER ApprovedPlan
    Supplies the immutable reviewed CopyGitHubRepo migration plan for Snapshot -IncludeReleases
    verification. ReleaseCheckpointPlan and ReleaseSelection from this object are consumed as
    expected evidence; verification does not rerun live source release selection or ordering.

    .PARAMETER HostName
    Specifies the GitHub host used for discovery and authentication. The default is github.com.

    .EXAMPLE
    Test-GitHubRepositoryMigration `
        -SourceRepository infoconex/source `
        -DestinationRepository infoconex/destination

    Verifies a plain Snapshot migration.

    .EXAMPLE
    Test-GitHubRepositoryMigration `
        -SourceRepository infoconex/source `
        -DestinationRepository infoconex/destination `
        -ContentMode FullHistory `
        -IncludeReleases `
        -ReleaseTag 'v2.*'

    Verifies FullHistory plus the selected live source releases using the existing contract.

    .EXAMPLE
    Test-GitHubRepositoryMigration `
        -SourceRepository infoconex/source `
        -DestinationRepository infoconex/destination `
        -ContentMode Snapshot `
        -IncludeReleases `
        -ApprovedPlan $migration.Plan

    Independently verifies Snapshot release checkpoints and recreated releases from the exact
    immutable evidence that was reviewed before migration execution.

    .INPUTS
    None. This command does not accept pipeline input.

    .OUTPUTS
    CopyGitHubRepo.MigrationVerificationResult. When release verification is requested, the
    result includes ReleaseVerification and ReleasesVerified and IsSuccessful reflects both
    Git content and GitHub Release verification.

    .LINK
    https://github.com/infoconex/copy-github-repo

    .LINK
    https://github.com/infoconex/copy-github-repo/blob/main/docs/product/product-contract.md
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidatePattern('^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$')]
        [string] $SourceRepository,

        [Parameter(Mandatory)]
        [ValidatePattern('^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$')]
        [string] $DestinationRepository,

        [ValidateSet('Snapshot', 'FullHistory')]
        [string] $ContentMode = 'Snapshot',

        [switch] $IncludeReleases,

        [string[]] $ReleaseTag,

        [string[]] $ReleaseExcludeTag,

        [switch] $IncludePrerelease,

        [switch] $IncludeDraftReleases,

        [ValidateRange(1, [int]::MaxValue)]
        [int] $ReleaseCount,

        [psobject] $ApprovedPlan,

        [ValidateNotNullOrEmpty()]
        [string] $HostName = 'github.com'
    )

    Assert-CgrSupportedHostName -HostName $HostName

    $releaseFilterWasSpecified = $PSBoundParameters.ContainsKey('ReleaseTag') -or
    $PSBoundParameters.ContainsKey('ReleaseExcludeTag') -or
    $IncludePrerelease -or
    $IncludeDraftReleases -or
    $PSBoundParameters.ContainsKey('ReleaseCount')

    if ($releaseFilterWasSpecified -and -not $IncludeReleases) {
        $message = 'Release verification filter parameters require -IncludeReleases.'
        $exception = [System.InvalidOperationException]::new($message)
        $errorRecord = [System.Management.Automation.ErrorRecord]::new(
            $exception,
            'ReleaseFilterRequiresIncludeReleases',
            [System.Management.Automation.ErrorCategory]::InvalidArgument,
            'IncludeReleases'
        )
        $PSCmdlet.ThrowTerminatingError($errorRecord)
    }

    if ($ApprovedPlan -and -not ($ContentMode -eq 'Snapshot' -and $IncludeReleases)) {
        $message = '-ApprovedPlan is supported only with -ContentMode Snapshot -IncludeReleases.'
        $exception = [System.InvalidOperationException]::new($message)
        $errorRecord = [System.Management.Automation.ErrorRecord]::new(
            $exception,
            'ApprovedPlanRequiresSnapshotReleaseVerification',
            [System.Management.Automation.ErrorCategory]::InvalidArgument,
            'ApprovedPlan'
        )
        $PSCmdlet.ThrowTerminatingError($errorRecord)
    }

    if ($ContentMode -eq 'Snapshot' -and $IncludeReleases) {
        if (-not $ApprovedPlan) {
            $message = 'Snapshot release verification requires -ApprovedPlan so expected checkpoints and releases come from immutable reviewed evidence.'
            $exception = [System.InvalidOperationException]::new($message)
            $errorRecord = [System.Management.Automation.ErrorRecord]::new(
                $exception,
                'SnapshotReleaseVerificationPlanRequired',
                [System.Management.Automation.ErrorCategory]::InvalidArgument,
                'ApprovedPlan'
            )
            $PSCmdlet.ThrowTerminatingError($errorRecord)
        }
        if ($releaseFilterWasSpecified) {
            $message = 'Snapshot release verification cannot use live release filters with -ApprovedPlan. The reviewed release selection is authoritative.'
            $exception = [System.InvalidOperationException]::new($message)
            $errorRecord = [System.Management.Automation.ErrorRecord]::new(
                $exception,
                'SnapshotReleaseVerificationFiltersNotAllowed',
                [System.Management.Automation.ErrorCategory]::InvalidArgument,
                'ApprovedPlan'
            )
            $PSCmdlet.ThrowTerminatingError($errorRecord)
        }

        $approvedContentMode = [string] (Get-CgrObjectProperty -InputObject $ApprovedPlan -Name 'ContentMode')
        $approvedIncludeReleases = [bool] (Get-CgrObjectProperty -InputObject $ApprovedPlan -Name 'IncludeReleases')
        $checkpointPlan = Get-CgrObjectProperty -InputObject $ApprovedPlan -Name 'ReleaseCheckpointPlan'
        $approvedSelection = Get-CgrObjectProperty -InputObject $ApprovedPlan -Name 'ReleaseSelection'
        if ($approvedContentMode -ne 'Snapshot' -or -not $approvedIncludeReleases -or $null -eq $checkpointPlan -or $null -eq $approvedSelection) {
            $message = 'The supplied approved plan does not contain complete Snapshot -IncludeReleases verification evidence.'
            $exception = [System.InvalidOperationException]::new($message)
            $errorRecord = [System.Management.Automation.ErrorRecord]::new(
                $exception,
                'SnapshotReleaseVerificationPlanInvalid',
                [System.Management.Automation.ErrorCategory]::InvalidData,
                'ApprovedPlan'
            )
            $PSCmdlet.ThrowTerminatingError($errorRecord)
        }
    }

    $prerequisites = Get-CgrPrerequisiteStatus -HostName $HostName
    if (-not $prerequisites.Git.Found) {
        $exception = [System.InvalidOperationException]::new(
            "Git is required for repository migration verification. Install 'git' from https://git-scm.com/."
        )
        $errorRecord = [System.Management.Automation.ErrorRecord]::new(
            $exception,
            'GitNotFound',
            [System.Management.Automation.ErrorCategory]::ResourceUnavailable,
            'git'
        )
        $PSCmdlet.ThrowTerminatingError($errorRecord)
    }

    if (-not $prerequisites.GitHubCli.Found) {
        $exception = [System.InvalidOperationException]::new(
            "GitHub CLI is required for repository migration verification. Install 'gh' from https://cli.github.com/."
        )
        $errorRecord = [System.Management.Automation.ErrorRecord]::new(
            $exception,
            'GitHubCliNotFound',
            [System.Management.Automation.ErrorCategory]::ResourceUnavailable,
            'gh'
        )
        $PSCmdlet.ThrowTerminatingError($errorRecord)
    }

    if (-not $prerequisites.Authentication.Authenticated) {
        $exception = [System.InvalidOperationException]::new($prerequisites.Authentication.Message)
        $errorRecord = [System.Management.Automation.ErrorRecord]::new(
            $exception,
            'GitHubCliNotAuthenticated',
            [System.Management.Automation.ErrorCategory]::SecurityError,
            $HostName
        )
        $PSCmdlet.ThrowTerminatingError($errorRecord)
    }

    if ($ContentMode -eq 'Snapshot' -and $IncludeReleases) {
        $destination = Get-CgrRepository -Repository $DestinationRepository -HostName $HostName
        $approvedSourceName = [string] (Get-CgrObjectProperty -InputObject $ApprovedPlan -Name 'SourceRepository')
        $source = [pscustomobject] @{
            FullName = if ([string]::IsNullOrWhiteSpace($approvedSourceName)) { $SourceRepository } else { $approvedSourceName }
            HostName = $HostName
        }

        $result = Invoke-CgrApprovedSnapshotReleaseVerification `
            -SourceRepository $source `
            -DestinationRepository $destination `
            -ReleaseCheckpointPlan $checkpointPlan

        $gitContentSuccessful = [bool] $result.IsSuccessful
        $targetEvidenceComplete = @($result.ReleaseTags).Count -eq @($approvedSelection.Releases).Count -and
        -not @($result.ReleaseTags | Where-Object { [string]::IsNullOrWhiteSpace([string] $_.DestinationCommitSha) })
        $releaseVerification = $null
        if ($targetEvidenceComplete) {
            $releaseVerification = Test-CgrGitHubReleaseMigration `
                -SourceRepository $source `
                -DestinationRepository $destination `
                -ApprovedSelection $approvedSelection `
                -DestinationTagTargets @($result.ReleaseTags) `
                -RequireExactDestinationReleaseSet `
                -HostName $HostName
        }

        $releasesVerified = [bool] ($releaseVerification -and $releaseVerification.IsSuccessful)
        $result | Add-Member -NotePropertyName GitContentSuccessful -NotePropertyValue $gitContentSuccessful -Force
        $result | Add-Member -NotePropertyName ReleaseVerification -NotePropertyValue $releaseVerification -Force
        $result | Add-Member -NotePropertyName ReleasesVerified -NotePropertyValue $releasesVerified -Force
        $result | Add-Member -NotePropertyName IsSuccessful -NotePropertyValue ([bool] ($gitContentSuccessful -and $releasesVerified)) -Force
        return $result
    }

    $source = Get-CgrRepository -Repository $SourceRepository -HostName $HostName
    $destination = Get-CgrRepository -Repository $DestinationRepository -HostName $HostName

    if ($ContentMode -eq 'FullHistory') {
        $result = Invoke-CgrRepositoryFullHistoryVerification `
            -SourceRepository $source `
            -DestinationRepository $destination

        if ($IncludeReleases) {
            $releaseVerificationParameters = @{
                SourceRepository = $source
                DestinationRepository = $destination
                ReleaseTag = $ReleaseTag
                ReleaseExcludeTag = $ReleaseExcludeTag
                IncludePrerelease = $IncludePrerelease
                IncludeDraftReleases = $IncludeDraftReleases
                HostName = $HostName
            }
            if ($PSBoundParameters.ContainsKey('ReleaseCount')) {
                $releaseVerificationParameters.ReleaseCount = $ReleaseCount
            }

            $gitContentSuccessful = [bool] $result.IsSuccessful
            $releaseVerification = Test-CgrGitHubReleaseMigration @releaseVerificationParameters
            $result | Add-Member -NotePropertyName GitContentSuccessful -NotePropertyValue $gitContentSuccessful -Force
            $result | Add-Member -NotePropertyName ReleaseVerification -NotePropertyValue $releaseVerification -Force
            $result | Add-Member -NotePropertyName ReleasesVerified -NotePropertyValue ([bool] $releaseVerification.IsSuccessful) -Force
            $result | Add-Member -NotePropertyName IsSuccessful -NotePropertyValue ([bool] ($gitContentSuccessful -and $releaseVerification.IsSuccessful)) -Force
        }

        return $result
    }

    Invoke-CgrRepositorySnapshotVerification `
        -SourceRepository $source `
        -DestinationRepository $destination
}