Private/Migration/Copy-CgrRepositorySnapshot.ps1
|
function Copy-CgrRepositorySnapshot { <# .SYNOPSIS Publishes an approved source tree as one clean Snapshot root commit. .DESCRIPTION Clones only the approved source branch, verifies the cloned commit and tree against reviewed source-state evidence when provided, creates an unrelated commit-tree root, transfers required Git LFS content, pushes the destination branch, and confirms the remote ref points to the new root commit. .NOTES This is a Git mutation boundary called after the public ShouldProcess decision. The temporary workspace is always removed. Approved-state mismatch stops before the destination branch is published so a reviewed plan cannot silently drift. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSUseShouldProcessForStateChangingFunctions', '', Justification = 'The public Copy-GitHubRepository command performs the ShouldProcess check before calling this Git boundary.' )] [CmdletBinding()] param( [Parameter(Mandatory)] [psobject] $SourceRepository, [Parameter(Mandatory)] [psobject] $DestinationRepository, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $BranchName, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $CommitMessage, [psobject] $ApprovedSourceState ) $workspacePath = Join-Path ([System.IO.Path]::GetTempPath()) "copy-github-repo-$([guid]::NewGuid().ToString('N'))" $sourcePath = Join-Path $workspacePath 'source' try { New-Item -Path $workspacePath -ItemType Directory -Force | Out-Null Send-CgrActivityEvent -Name 'CloneSourceContent' -State Started -Message "Clone source content from '$($SourceRepository.FullName)'" $cloneSourceResult = Invoke-CgrGitCommand ` -HostName $SourceRepository.HostName ` -Environment @{ GIT_LFS_SKIP_SMUDGE = '1' } ` -ArgumentList @('clone', '--depth', '1', '--branch', $BranchName, $SourceRepository.CloneUrl, $sourcePath) if ($cloneSourceResult.ExitCode -ne 0) { Send-CgrActivityEvent -Name 'CloneSourceContent' -State Failed -Message "Clone source content from '$($SourceRepository.FullName)'" $message = "Git failed to clone source repository '$($SourceRepository.FullName)'. $($cloneSourceResult.ErrorText)" $exception = [System.InvalidOperationException]::new($message.Trim()) $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, 'SourceRepositoryCloneFailed', [System.Management.Automation.ErrorCategory]::InvalidOperation, $SourceRepository.FullName) $PSCmdlet.ThrowTerminatingError($errorRecord) } Send-CgrActivityEvent -Name 'CloneSourceContent' -State Completed -Message "Clone source content from '$($SourceRepository.FullName)'" $sourceCommitResult = Invoke-CgrNativeCommand -FilePath 'git' -ArgumentList @('-C', $sourcePath, 'rev-parse', 'HEAD') if ($sourceCommitResult.ExitCode -ne 0) { $message = "Git failed to read source commit for '$($SourceRepository.FullName)'. $($sourceCommitResult.ErrorText)" $exception = [System.InvalidOperationException]::new($message.Trim()) $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, 'SourceRepositoryCommitReadFailed', [System.Management.Automation.ErrorCategory]::InvalidData, $SourceRepository.FullName) $PSCmdlet.ThrowTerminatingError($errorRecord) } $sourceCommitSha = [string] @($sourceCommitResult.Output)[0] $treeShaResult = Invoke-CgrNativeCommand -FilePath 'git' -ArgumentList @('-C', $sourcePath, 'rev-parse', 'HEAD^{tree}') if ($treeShaResult.ExitCode -ne 0) { $message = "Git failed to read source tree for '$($SourceRepository.FullName)'. $($treeShaResult.ErrorText)" $exception = [System.InvalidOperationException]::new($message.Trim()) $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, 'SourceRepositoryTreeReadFailed', [System.Management.Automation.ErrorCategory]::InvalidData, $SourceRepository.FullName) $PSCmdlet.ThrowTerminatingError($errorRecord) } $treeSha = [string] @($treeShaResult.Output)[0] if ($ApprovedSourceState) { $expectedCommitSha = [string] (Get-CgrObjectProperty -InputObject $ApprovedSourceState -Name 'CommitSha') $expectedTreeSha = [string] (Get-CgrObjectProperty -InputObject $ApprovedSourceState -Name 'TreeSha') $expectedBranch = [string] (Get-CgrObjectProperty -InputObject $ApprovedSourceState -Name 'DefaultBranch') if ($BranchName -ne $expectedBranch -or $sourceCommitSha -ne $expectedCommitSha -or $treeSha -ne $expectedTreeSha) { $message = "Source repository '$($SourceRepository.FullName)' no longer matches the approved Snapshot source state. Nothing was published. Recreate and review the repository copy plan." $exception = [System.InvalidOperationException]::new($message) $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, 'SourceStateChangedSincePlanning', [System.Management.Automation.ErrorCategory]::InvalidResult, $SourceRepository.FullName) $PSCmdlet.ThrowTerminatingError($errorRecord) } } $commitIdentity = Get-CgrGitCommitIdentity -HostName $DestinationRepository.HostName Send-CgrActivityEvent -Name 'PublishSnapshot' -State Started -Message "Publish clean Snapshot to '$($DestinationRepository.FullName)'" $commitResult = Invoke-CgrNativeCommand ` -FilePath 'git' ` -ArgumentList @('-C', $sourcePath, '-c', "user.name=$($commitIdentity.Name)", '-c', "user.email=$($commitIdentity.Email)", 'commit-tree', $treeSha, '-m', $CommitMessage) if ($commitResult.ExitCode -ne 0) { Send-CgrActivityEvent -Name 'PublishSnapshot' -State Failed -Message "Publish clean Snapshot to '$($DestinationRepository.FullName)'" $message = "Git failed to create snapshot commit for '$($DestinationRepository.FullName)'. $($commitResult.ErrorText)" $exception = [System.InvalidOperationException]::new($message.Trim()) $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, 'DestinationRepositorySnapshotCommitFailed', [System.Management.Automation.ErrorCategory]::InvalidOperation, $DestinationRepository.FullName) $PSCmdlet.ThrowTerminatingError($errorRecord) } $commitSha = [string] @($commitResult.Output)[0] $gitLfs = Invoke-CgrActivityStage ` -Name 'TransferGitLfs' ` -Message "Transfer Git LFS objects to '$($DestinationRepository.FullName)'" ` -Action { Copy-CgrGitLfsObject -SourcePath $sourcePath -SourceRepository $SourceRepository -DestinationRepository $DestinationRepository -BranchName $BranchName } $pushRef = "$($commitSha):refs/heads/$BranchName" $pushResult = Invoke-CgrGitCommand -HostName $DestinationRepository.HostName -ArgumentList @('-C', $sourcePath, 'push', $DestinationRepository.CloneUrl, $pushRef) if ($pushResult.ExitCode -ne 0) { Send-CgrActivityEvent -Name 'PublishSnapshot' -State Failed -Message "Publish clean Snapshot to '$($DestinationRepository.FullName)'" $message = "Git failed to push snapshot commit to '$($DestinationRepository.FullName)'. $($pushResult.ErrorText)" $exception = [System.InvalidOperationException]::new($message.Trim()) $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, 'DestinationRepositorySnapshotPushFailed', [System.Management.Automation.ErrorCategory]::InvalidOperation, $DestinationRepository.FullName) $PSCmdlet.ThrowTerminatingError($errorRecord) } $remoteRefResult = Invoke-CgrGitCommand -HostName $DestinationRepository.HostName -ArgumentList @('ls-remote', '--heads', $DestinationRepository.CloneUrl, $BranchName) if ($remoteRefResult.ExitCode -ne 0) { Send-CgrActivityEvent -Name 'PublishSnapshot' -State Failed -Message "Publish clean Snapshot to '$($DestinationRepository.FullName)'" $message = "Git failed to verify destination branch '$BranchName' for '$($DestinationRepository.FullName)'. $($remoteRefResult.ErrorText)" $exception = [System.InvalidOperationException]::new($message.Trim()) $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, 'DestinationRepositorySnapshotVerifyFailed', [System.Management.Automation.ErrorCategory]::InvalidOperation, $DestinationRepository.FullName) $PSCmdlet.ThrowTerminatingError($errorRecord) } $remoteRef = (@($remoteRefResult.Output) | ForEach-Object { $_.ToString() }) -join [Environment]::NewLine if ($remoteRef -notmatch [regex]::Escape($commitSha)) { Send-CgrActivityEvent -Name 'PublishSnapshot' -State Failed -Message "Publish clean Snapshot to '$($DestinationRepository.FullName)'" $message = "Destination branch '$BranchName' does not reference snapshot commit '$commitSha'." $exception = [System.InvalidOperationException]::new($message) $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, 'DestinationRepositorySnapshotVerifyMismatch', [System.Management.Automation.ErrorCategory]::InvalidData, $DestinationRepository.FullName) $PSCmdlet.ThrowTerminatingError($errorRecord) } Send-CgrActivityEvent -Name 'PublishSnapshot' -State Completed -Message "Publish clean Snapshot to '$($DestinationRepository.FullName)'" return [pscustomobject] @{ PSTypeName = 'CopyGitHubRepo.SnapshotCopyResult' SourceRepository = $SourceRepository.FullName SourceCommitSha = $sourceCommitSha DestinationRepository = $DestinationRepository.FullName BranchName = $BranchName CommitSha = $commitSha TreeSha = $treeSha GitLfs = $gitLfs ApprovedSourceState = $ApprovedSourceState Verified = $true } } finally { if (Test-Path -LiteralPath $workspacePath) { Remove-Item -LiteralPath $workspacePath -Recurse -Force -ErrorAction SilentlyContinue } } } |