Public/New-Release.ps1
|
function New-Release { <# .SYNOPSIS Mark the current saved point as a named release version. .DESCRIPTION New-Release creates a permanent, annotated marker at the current saved point - the GitEasy term for what Git calls an "annotated tag". Use it to stamp a release version (v1.0.0, v1.5.0, etc.) with a note that travels alongside the project. By default, New-Release also publishes the marker to the project's published location. Use -NoPush to keep it local. If a release of the same version already exists, New-Release refuses to overwrite unless you pass -Force. Each invocation writes a self-contained diagnostic log file. Successful runs log silently; failures throw a plain-English message and point at the log file with the technical detail. .PARAMETER Version The release version name. Conventional format is `v` followed by major.minor.patch (for example, `v1.5.0`), but any valid name is accepted. .PARAMETER Note A short message describing the release. Travels permanently with the marker; appears in `Show-Releases` output. .PARAMETER NoPush Create the release marker locally only. Do not publish. .PARAMETER Force Overwrite an existing release of the same version. Without -Force, New-Release refuses to clobber an existing release. .PARAMETER LogPath Override the directory where the diagnostic log for this run is written. .EXAMPLE New-Release -Version v1.5.0 -Note 'Phase 15 complete: ...' .EXAMPLE New-Release v1.5.0 'phase 15' -NoPush .EXAMPLE New-Release -Version v1.5.0 -Note 'corrected release note' -Force .NOTES Safety: - Refuses to run during an unfinished merge, rebase, cherry-pick, revert, or bisect. - Refuses to run while there are unfinished conflicts. - Refuses to overwrite an existing release without -Force. - Writes the release note without UTF-8 BOM. .LINK Show-Releases .LINK Save-Work .LINK Show-History #> [CmdletBinding(SupportsShouldProcess)] [OutputType([PSCustomObject])] param( [Parameter(Mandatory, Position = 0)] [string]$Version, [Parameter(Mandatory, Position = 1)] [string]$Note, [Parameter()] [switch]$NoPush, [Parameter()] [switch]$Force, [Parameter()] [string]$LogPath = '' ) $repoRoot = $null try { $rootProbe = Invoke-GEGit -ArgumentList @('rev-parse', '--show-toplevel') -AllowFailure if ($rootProbe.ExitCode -eq 0) { $repoRoot = $rootProbe.Output | Select-Object -First 1 } } catch { $repoRoot = $null } $session = Start-GELogSession -Command 'New-Release' -Repository ([string]$repoRoot) -LogPath $LogPath $userMessageOnFailure = "Could not create the release '$Version'." try { Assert-GESafeSave -Path ([string]$repoRoot) -LogPath $session.Path | Out-Null if (-not $repoRoot) { $rootResult = Invoke-GEGit -ArgumentList @('rev-parse', '--show-toplevel') -LogPath $session.Path $repoRoot = $rootResult.Output | Select-Object -First 1 } $checkRef = Invoke-GEGit -ArgumentList @('check-ref-format', "refs/tags/$Version") -WorkingDirectory $repoRoot -LogPath $session.Path -AllowFailure if ($checkRef.ExitCode -ne 0) { throw "'$Version' is not a valid release name. Use letters, digits, dashes, slashes, underscores, and dots (for example, v1.5.0)." } $existCheck = Invoke-GEGit -ArgumentList @('rev-parse', '--verify', '--quiet', "refs/tags/$Version") -WorkingDirectory $repoRoot -LogPath $session.Path -AllowFailure $existsLocally = ($existCheck.ExitCode -eq 0) if ($existsLocally -and -not $Force) { throw "A release named '$Version' already exists. Use -Force to overwrite, or pick a different version name." } if (-not $PSCmdlet.ShouldProcess($repoRoot, "Create release '$Version'")) { Complete-GELogSession -Path $session.Path -Outcome 'SUCCESS' -UserMessage 'Skipped (WhatIf).' return } if ($existsLocally -and $Force) { Invoke-GEGit -ArgumentList @('tag', '-d', $Version) -WorkingDirectory $repoRoot -LogPath $session.Path | Out-Null } $noteFile = Join-Path ([System.IO.Path]::GetTempPath()) ('GitEasyRelease_' + [guid]::NewGuid().ToString('N') + '.txt') try { [System.IO.File]::WriteAllText($noteFile, $Note, [System.Text.UTF8Encoding]::new($false)) Invoke-GEGit -ArgumentList @('tag', '-a', $Version, '-F', $noteFile) -WorkingDirectory $repoRoot -LogPath $session.Path | Out-Null } finally { Remove-Item -LiteralPath $noteFile -Force -ErrorAction SilentlyContinue } Write-Host "Created release '$Version'." $remoteName = '' if (-not $NoPush) { $remoteResult = Invoke-GEGit -ArgumentList @('remote') -WorkingDirectory $repoRoot -LogPath $session.Path -AllowFailure $remotes = @($remoteResult.Output | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) if ($remotes.Count -eq 0) { Write-Host 'No published location is configured. Release marker is local only.' } else { if ($remotes -contains 'origin') { $remoteName = 'origin' } else { $remoteName = $remotes[0] } $userMessageOnFailure = "Could not publish the release '$Version'." $pushArgs = @('push', $remoteName, $Version) if ($Force) { $pushArgs += '--force' } Invoke-GEGit -ArgumentList $pushArgs -WorkingDirectory $repoRoot -LogPath $session.Path | Out-Null Write-Host "Published release '$Version' to '$remoteName'." } } else { Write-Host 'Saved locally only - the release has not been published.' } $result = [PSCustomObject]@{ Repository = $repoRoot Version = $Version Note = $Note Published = (-not $NoPush -and -not [string]::IsNullOrWhiteSpace($remoteName)) Message = "Release '$Version' created." } Complete-GELogSession -Path $session.Path -Outcome 'SUCCESS' return $result } catch { $err = $_ $innerMessage = $err.Exception.Message if ($innerMessage -like 'git *') { $finalMsg = $userMessageOnFailure } else { $finalMsg = $innerMessage } Complete-GELogSession -Path $session.Path -Outcome 'FAILURE' -UserMessage $finalMsg -ErrorMessage $innerMessage throw "$finalMsg Details: $($session.Path)" } } |