Private/Migration/Invoke-CgrRepositoryFullHistoryVerification.ps1

function Invoke-CgrRepositoryFullHistoryVerification {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [psobject] $SourceRepository,

        [Parameter(Mandatory)]
        [psobject] $DestinationRepository
    )

    $workspacePath = Join-Path ([System.IO.Path]::GetTempPath()) "copy-github-repo-fullhistory-verify-$([guid]::NewGuid().ToString('N'))"
    $sourcePath = Join-Path $workspacePath 'source.git'
    $destinationPath = Join-Path $workspacePath 'destination.git'

    try {
        New-Item -Path $workspacePath -ItemType Directory -Force | Out-Null

        $gitLfsVersionResult = Invoke-CgrNativeCommand -FilePath 'git' -ArgumentList @('lfs', 'version')
        if ($gitLfsVersionResult.ExitCode -ne 0) {
            $message = 'Git LFS is required for FullHistory verification so object availability can be checked across all branches and tags.'
            $exception = [System.InvalidOperationException]::new($message)
            $errorRecord = [System.Management.Automation.ErrorRecord]::new(
                $exception,
                'GitLfsRequiredForFullHistoryVerification',
                [System.Management.Automation.ErrorCategory]::ResourceUnavailable,
                'git-lfs'
            )
            $PSCmdlet.ThrowTerminatingError($errorRecord)
        }

        $sourceCloneResult = Invoke-CgrGitCommand `
            -HostName $SourceRepository.HostName `
            -ArgumentList @('clone', '--bare', $SourceRepository.CloneUrl, $sourcePath)
        if ($sourceCloneResult.ExitCode -ne 0) {
            $message = "Git failed to clone source '$($SourceRepository.FullName)' for FullHistory verification. $($sourceCloneResult.ErrorText)"
            $exception = [System.InvalidOperationException]::new($message.Trim())
            $errorRecord = [System.Management.Automation.ErrorRecord]::new(
                $exception,
                'FullHistoryVerificationSourceCloneFailed',
                [System.Management.Automation.ErrorCategory]::InvalidOperation,
                $SourceRepository.FullName
            )
            $PSCmdlet.ThrowTerminatingError($errorRecord)
        }

        $destinationCloneResult = Invoke-CgrGitCommand `
            -HostName $DestinationRepository.HostName `
            -ArgumentList @('clone', '--bare', $DestinationRepository.CloneUrl, $destinationPath)
        if ($destinationCloneResult.ExitCode -ne 0) {
            $message = "Git failed to clone destination '$($DestinationRepository.FullName)' for FullHistory verification. $($destinationCloneResult.ErrorText)"
            $exception = [System.InvalidOperationException]::new($message.Trim())
            $errorRecord = [System.Management.Automation.ErrorRecord]::new(
                $exception,
                'FullHistoryVerificationDestinationCloneFailed',
                [System.Management.Automation.ErrorCategory]::InvalidOperation,
                $DestinationRepository.FullName
            )
            $PSCmdlet.ThrowTerminatingError($errorRecord)
        }

        $sourceLfsResult = Invoke-CgrGitCommand `
            -HostName $SourceRepository.HostName `
            -ArgumentList @('-C', $sourcePath, 'lfs', 'fetch', '--all', 'origin')
        $destinationLfsResult = Invoke-CgrGitCommand `
            -HostName $DestinationRepository.HostName `
            -ArgumentList @('-C', $destinationPath, 'lfs', 'fetch', '--all', 'origin')

        $sourceRefsResult = Invoke-CgrNativeCommand `
            -FilePath 'git' `
            -ArgumentList @('-C', $sourcePath, 'for-each-ref', '--format=%(refname) %(objectname)', 'refs/heads', 'refs/tags')
        $destinationRefsResult = Invoke-CgrNativeCommand `
            -FilePath 'git' `
            -ArgumentList @('-C', $destinationPath, 'for-each-ref', '--format=%(refname) %(objectname)', 'refs/heads', 'refs/tags')
        $sourceCommitCountResult = Invoke-CgrNativeCommand `
            -FilePath 'git' `
            -ArgumentList @('-C', $sourcePath, 'rev-list', '--all', '--count')
        $destinationCommitCountResult = Invoke-CgrNativeCommand `
            -FilePath 'git' `
            -ArgumentList @('-C', $destinationPath, 'rev-list', '--all', '--count')
        $sourceBranchesResult = Invoke-CgrNativeCommand `
            -FilePath 'git' `
            -ArgumentList @('-C', $sourcePath, 'for-each-ref', '--format=%(refname)', 'refs/heads')

        $verificationCommands = @(
            [pscustomobject] @{ Result = $sourceLfsResult; ErrorId = 'FullHistoryVerificationSourceGitLfsFetchFailed'; Target = $SourceRepository.FullName }
            [pscustomobject] @{ Result = $destinationLfsResult; ErrorId = 'FullHistoryVerificationDestinationGitLfsFetchFailed'; Target = $DestinationRepository.FullName }
            [pscustomobject] @{ Result = $sourceRefsResult; ErrorId = 'FullHistoryVerificationSourceRefsReadFailed'; Target = $SourceRepository.FullName }
            [pscustomobject] @{ Result = $destinationRefsResult; ErrorId = 'FullHistoryVerificationDestinationRefsReadFailed'; Target = $DestinationRepository.FullName }
            [pscustomobject] @{ Result = $sourceCommitCountResult; ErrorId = 'FullHistoryVerificationSourceCommitCountReadFailed'; Target = $SourceRepository.FullName }
            [pscustomobject] @{ Result = $destinationCommitCountResult; ErrorId = 'FullHistoryVerificationDestinationCommitCountReadFailed'; Target = $DestinationRepository.FullName }
            [pscustomobject] @{ Result = $sourceBranchesResult; ErrorId = 'FullHistoryVerificationSourceBranchesReadFailed'; Target = $SourceRepository.FullName }
        )

        foreach ($command in $verificationCommands) {
            if ($command.Result.ExitCode -ne 0) {
                $message = "Git FullHistory verification command failed for '$($command.Target)'. $($command.Result.ErrorText)"
                $exception = [System.InvalidOperationException]::new($message.Trim())
                $errorRecord = [System.Management.Automation.ErrorRecord]::new(
                    $exception,
                    $command.ErrorId,
                    [System.Management.Automation.ErrorCategory]::InvalidOperation,
                    $command.Target
                )
                $PSCmdlet.ThrowTerminatingError($errorRecord)
            }
        }

        $sourceRefs = @($sourceRefsResult.Output | ForEach-Object { $_.ToString() } | Sort-Object)
        $destinationRefs = @($destinationRefsResult.Output | ForEach-Object { $_.ToString() } | Sort-Object)
        $sourceCommitCount = [int] ([string] @($sourceCommitCountResult.Output)[0])
        $destinationCommitCount = [int] ([string] @($destinationCommitCountResult.Output)[0])
        $branches = @($sourceBranchesResult.Output | ForEach-Object { $_.ToString() } | Sort-Object)

        $sourceBranchTrees = [System.Collections.Generic.List[string]]::new()
        $destinationBranchTrees = [System.Collections.Generic.List[string]]::new()
        foreach ($branch in $branches) {
            $sourceTreeResult = Invoke-CgrNativeCommand `
                -FilePath 'git' `
                -ArgumentList @('-C', $sourcePath, 'rev-parse', "$branch^{tree}")
            $destinationTreeResult = Invoke-CgrNativeCommand `
                -FilePath 'git' `
                -ArgumentList @('-C', $destinationPath, 'rev-parse', "$branch^{tree}")

            if ($sourceTreeResult.ExitCode -ne 0 -or $destinationTreeResult.ExitCode -ne 0) {
                $message = "Git failed to read branch-tip tree '$branch' during FullHistory verification."
                $exception = [System.InvalidOperationException]::new($message)
                $errorRecord = [System.Management.Automation.ErrorRecord]::new(
                    $exception,
                    'FullHistoryVerificationBranchTreeReadFailed',
                    [System.Management.Automation.ErrorCategory]::InvalidOperation,
                    $branch
                )
                $PSCmdlet.ThrowTerminatingError($errorRecord)
            }

            $sourceBranchTrees.Add("$branch $([string] @($sourceTreeResult.Output)[0])")
            $destinationBranchTrees.Add("$branch $([string] @($destinationTreeResult.Output)[0])")
        }

        $refsMatch = ($sourceRefs -join "`n") -eq ($destinationRefs -join "`n")
        $branchTreesMatch = (($sourceBranchTrees.ToArray() | Sort-Object) -join "`n") -eq (($destinationBranchTrees.ToArray() | Sort-Object) -join "`n")

        $checks = @(
            [pscustomobject] @{
                Name = 'DestinationExists'
                Passed = $true
                Expected = $DestinationRepository.FullName
                Actual = $DestinationRepository.FullName
            }
            [pscustomobject] @{
                Name = 'DefaultBranchMatches'
                Passed = $DestinationRepository.DefaultBranch -eq $SourceRepository.DefaultBranch
                Expected = $SourceRepository.DefaultBranch
                Actual = $DestinationRepository.DefaultBranch
            }
            [pscustomobject] @{
                Name = 'BranchAndTagTargetsMatch'
                Passed = $refsMatch
                Expected = $sourceRefs
                Actual = $destinationRefs
            }
            [pscustomobject] @{
                Name = 'ReachableCommitCountMatches'
                Passed = $destinationCommitCount -eq $sourceCommitCount
                Expected = $sourceCommitCount
                Actual = $destinationCommitCount
            }
            [pscustomobject] @{
                Name = 'BranchTipTreesMatch'
                Passed = $branchTreesMatch
                Expected = $sourceBranchTrees.ToArray()
                Actual = $destinationBranchTrees.ToArray()
            }
            [pscustomobject] @{
                Name = 'GitLfsObjectsAvailable'
                Passed = $sourceLfsResult.ExitCode -eq 0 -and $destinationLfsResult.ExitCode -eq 0
                Expected = $true
                Actual = $sourceLfsResult.ExitCode -eq 0 -and $destinationLfsResult.ExitCode -eq 0
            }
        )

        [pscustomobject] @{
            PSTypeName = 'CopyGitHubRepo.MigrationVerificationResult'
            SchemaVersion = 1
            ContentMode = 'FullHistory'
            SourceRepository = $SourceRepository.FullName
            DestinationRepository = $DestinationRepository.FullName
            BranchName = $SourceRepository.DefaultBranch
            SourceRefCount = $sourceRefs.Count
            DestinationRefCount = $destinationRefs.Count
            SourceReachableCommitCount = $sourceCommitCount
            DestinationReachableCommitCount = $destinationCommitCount
            IsSuccessful = -not ($checks | Where-Object { -not $_.Passed })
            Checks = $checks
        }
    }
    finally {
        if (Test-Path -LiteralPath $workspacePath) {
            Remove-Item -LiteralPath $workspacePath -Recurse -Force -ErrorAction SilentlyContinue
        }
    }
}