Private/Migration/Copy-CgrRepositoryFullHistory.ps1
|
function Copy-CgrRepositoryFullHistory { <# .SYNOPSIS Copies approved Git history, refs, and reachable Git LFS objects to a destination. .DESCRIPTION Creates a temporary bare clone, validates the workspace against reviewed FullHistory evidence when supplied, transfers reachable Git LFS objects, pushes all branches and tags, sets the destination default branch, and returns copied source evidence for later verification. .NOTES This is a Git/GitHub mutation boundary entered only after the public ShouldProcess decision. Git LFS is mandatory in FullHistory mode so reachable LFS availability can be preserved rather than silently omitted. Temporary workspace cleanup runs regardless of success or failure. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSUseShouldProcessForStateChangingFunctions', '', Justification = 'The public Copy-GitHubRepository command performs the ShouldProcess check before calling this migration boundary.' )] [CmdletBinding()] param( [Parameter(Mandatory)] [psobject] $SourceRepository, [Parameter(Mandatory)] [psobject] $DestinationRepository, [ValidateNotNullOrEmpty()] [string] $HostName = 'github.com', [psobject] $ApprovedSourceState ) $workspacePath = Join-Path ([System.IO.Path]::GetTempPath()) "copy-github-repo-fullhistory-$([guid]::NewGuid().ToString('N'))" $sourcePath = Join-Path $workspacePath 'source.git' $destinationRemoteName = 'cgr-destination' 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 migration so LFS object availability can be preserved and verified across all branches and tags.' $exception = [System.InvalidOperationException]::new($message) $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, 'GitLfsRequiredForFullHistory', [System.Management.Automation.ErrorCategory]::ResourceUnavailable, 'git-lfs') $PSCmdlet.ThrowTerminatingError($errorRecord) } Send-CgrActivityEvent -Name 'CloneFullHistory' -State Started -Message "Clone full Git history from '$($SourceRepository.FullName)'" $cloneResult = Invoke-CgrGitCommand -HostName $SourceRepository.HostName -ArgumentList @('clone', '--bare', $SourceRepository.CloneUrl, $sourcePath) if ($cloneResult.ExitCode -ne 0) { Send-CgrActivityEvent -Name 'CloneFullHistory' -State Failed -Message "Clone full Git history from '$($SourceRepository.FullName)'" $message = "Git failed to clone full history from '$($SourceRepository.FullName)'. $($cloneResult.ErrorText)" $exception = [System.InvalidOperationException]::new($message.Trim()) $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, 'SourceFullHistoryCloneFailed', [System.Management.Automation.ErrorCategory]::InvalidOperation, $SourceRepository.FullName) $PSCmdlet.ThrowTerminatingError($errorRecord) } Send-CgrActivityEvent -Name 'CloneFullHistory' -State Completed -Message "Clone full Git history from '$($SourceRepository.FullName)'" Send-CgrActivityEvent -Name 'InspectGitLfs' -State Started -Message "Inspect Git LFS content in '$($SourceRepository.FullName)'" $lfsListResult = Invoke-CgrNativeCommand -FilePath 'git' -ArgumentList @('-C', $sourcePath, 'lfs', 'ls-files', '--all', '--name-only') if ($lfsListResult.ExitCode -ne 0) { Send-CgrActivityEvent -Name 'InspectGitLfs' -State Failed -Message "Inspect Git LFS content in '$($SourceRepository.FullName)'" $message = "Git LFS failed to enumerate tracked paths in '$($SourceRepository.FullName)'. $($lfsListResult.ErrorText)" $exception = [System.InvalidOperationException]::new($message.Trim()) $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, 'SourceFullHistoryGitLfsListFailed', [System.Management.Automation.ErrorCategory]::InvalidOperation, $SourceRepository.FullName) $PSCmdlet.ThrowTerminatingError($errorRecord) } $lfsPaths = @($lfsListResult.Output | Where-Object { -not [string]::IsNullOrWhiteSpace([string] $_) }) $sourceUsesGitLfs = $lfsPaths.Count -gt 0 $lfsInspectionMessage = if ($sourceUsesGitLfs) { "Found Git LFS content for $($lfsPaths.Count) tracked path(s)." } else { 'No Git LFS objects found; no transfer required.' } Send-CgrActivityEvent -Name 'InspectGitLfs' -State Completed -Message $lfsInspectionMessage if ($sourceUsesGitLfs) { Send-CgrActivityEvent -Name 'FetchGitLfs' -State Started -Message "Fetch reachable Git LFS objects from '$($SourceRepository.FullName)'" $lfsFetchResult = Invoke-CgrGitCommand -HostName $SourceRepository.HostName -ArgumentList @('-C', $sourcePath, 'lfs', 'fetch', '--all', 'origin') if ($lfsFetchResult.ExitCode -ne 0) { Send-CgrActivityEvent -Name 'FetchGitLfs' -State Failed -Message "Fetch reachable Git LFS objects from '$($SourceRepository.FullName)'" $message = "Git LFS failed to fetch all reachable objects from '$($SourceRepository.FullName)'. $($lfsFetchResult.ErrorText)" $exception = [System.InvalidOperationException]::new($message.Trim()) $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, 'SourceFullHistoryGitLfsFetchFailed', [System.Management.Automation.ErrorCategory]::InvalidOperation, $SourceRepository.FullName) $PSCmdlet.ThrowTerminatingError($errorRecord) } Send-CgrActivityEvent -Name 'FetchGitLfs' -State Completed -Message "Fetched reachable Git LFS objects from '$($SourceRepository.FullName)'" } $copiedSourceEvidence = $null if ($ApprovedSourceState) { $copiedSourceEvidence = Assert-CgrFullHistoryWorkspaceState ` -RepositoryPath $sourcePath ` -ApprovedSourceState $ApprovedSourceState ` -RepositoryName $SourceRepository.FullName } $addRemoteResult = Invoke-CgrNativeCommand -FilePath 'git' -ArgumentList @('-C', $sourcePath, 'remote', 'add', $destinationRemoteName, $DestinationRepository.CloneUrl) if ($addRemoteResult.ExitCode -ne 0) { $message = "Git failed to configure destination '$($DestinationRepository.FullName)' for FullHistory migration. $($addRemoteResult.ErrorText)" $exception = [System.InvalidOperationException]::new($message.Trim()) $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, 'DestinationFullHistoryRemoteConfigurationFailed', [System.Management.Automation.ErrorCategory]::InvalidOperation, $DestinationRepository.FullName) $PSCmdlet.ThrowTerminatingError($errorRecord) } if ($sourceUsesGitLfs) { Send-CgrActivityEvent -Name 'PushGitLfs' -State Started -Message "Publish Git LFS objects to '$($DestinationRepository.FullName)'" $lfsPushResult = Invoke-CgrGitCommand -HostName $DestinationRepository.HostName -ArgumentList @('-C', $sourcePath, 'lfs', 'push', '--all', $destinationRemoteName) if ($lfsPushResult.ExitCode -ne 0) { Send-CgrActivityEvent -Name 'PushGitLfs' -State Failed -Message "Publish Git LFS objects to '$($DestinationRepository.FullName)'" $message = "Git LFS failed to push all reachable objects to '$($DestinationRepository.FullName)'. $($lfsPushResult.ErrorText)" $exception = [System.InvalidOperationException]::new($message.Trim()) $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, 'DestinationFullHistoryGitLfsPushFailed', [System.Management.Automation.ErrorCategory]::InvalidOperation, $DestinationRepository.FullName) $PSCmdlet.ThrowTerminatingError($errorRecord) } Send-CgrActivityEvent -Name 'PushGitLfs' -State Completed -Message "Published Git LFS objects to '$($DestinationRepository.FullName)'" } Send-CgrActivityEvent -Name 'PublishFullHistoryBranches' -State Started -Message "Publish FullHistory branches to '$($DestinationRepository.FullName)'" $branchPushResult = Invoke-CgrGitCommand -HostName $DestinationRepository.HostName -ArgumentList @('-C', $sourcePath, 'push', $destinationRemoteName, '--all') if ($branchPushResult.ExitCode -ne 0) { Send-CgrActivityEvent -Name 'PublishFullHistoryBranches' -State Failed -Message "Publish FullHistory branches to '$($DestinationRepository.FullName)'" $message = "Git failed to push branches to '$($DestinationRepository.FullName)'. $($branchPushResult.ErrorText)" $exception = [System.InvalidOperationException]::new($message.Trim()) $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, 'DestinationFullHistoryBranchPushFailed', [System.Management.Automation.ErrorCategory]::InvalidOperation, $DestinationRepository.FullName) $PSCmdlet.ThrowTerminatingError($errorRecord) } Send-CgrActivityEvent -Name 'PublishFullHistoryBranches' -State Completed -Message "Publish FullHistory branches to '$($DestinationRepository.FullName)'" Send-CgrActivityEvent -Name 'PublishFullHistoryTags' -State Started -Message "Publish FullHistory tags to '$($DestinationRepository.FullName)'" $tagPushResult = Invoke-CgrGitCommand -HostName $DestinationRepository.HostName -ArgumentList @('-C', $sourcePath, 'push', $destinationRemoteName, '--tags') if ($tagPushResult.ExitCode -ne 0) { Send-CgrActivityEvent -Name 'PublishFullHistoryTags' -State Failed -Message "Publish FullHistory tags to '$($DestinationRepository.FullName)'" $message = "Git failed to push tags to '$($DestinationRepository.FullName)'. $($tagPushResult.ErrorText)" $exception = [System.InvalidOperationException]::new($message.Trim()) $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, 'DestinationFullHistoryTagPushFailed', [System.Management.Automation.ErrorCategory]::InvalidOperation, $DestinationRepository.FullName) $PSCmdlet.ThrowTerminatingError($errorRecord) } Send-CgrActivityEvent -Name 'PublishFullHistoryTags' -State Completed -Message "Publish FullHistory tags to '$($DestinationRepository.FullName)'" $defaultBranchResult = Invoke-CgrNativeCommand ` -FilePath 'gh' ` -ArgumentList @('api', '--hostname', $HostName, '-X', 'PATCH', "repos/$($DestinationRepository.FullName)", '-f', "default_branch=$($SourceRepository.DefaultBranch)") if ($defaultBranchResult.ExitCode -ne 0) { $message = "GitHub failed to set default branch '$($SourceRepository.DefaultBranch)' on '$($DestinationRepository.FullName)'. $($defaultBranchResult.ErrorText)" $exception = [System.InvalidOperationException]::new($message.Trim()) $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, 'DestinationFullHistoryDefaultBranchUpdateFailed', [System.Management.Automation.ErrorCategory]::InvalidOperation, $DestinationRepository.FullName) $PSCmdlet.ThrowTerminatingError($errorRecord) } $verifiedDestination = Get-CgrRepository -Repository $DestinationRepository.FullName -HostName $HostName [pscustomobject] @{ PSTypeName = 'CopyGitHubRepo.FullHistoryCopyResult' SourceRepository = $SourceRepository.FullName DestinationRepository = $verifiedDestination.FullName DefaultBranch = $verifiedDestination.DefaultBranch ApprovedSourceState = $ApprovedSourceState CopiedSourceEvidence = $copiedSourceEvidence BranchesCopied = $true TagsCopied = $true SourceUsesGitLfs = $sourceUsesGitLfs GitLfsTrackedPaths = @($lfsPaths) GitLfsObjectsCopied = $sourceUsesGitLfs IsSuccessful = $true } } finally { if (Test-Path -LiteralPath $workspacePath) { Remove-Item -LiteralPath $workspacePath -Recurse -Force -ErrorAction SilentlyContinue } } } |