Public/Undo-Changes.ps1
|
function Undo-Changes { <# .SYNOPSIS Throw away unsaved changes and return the working area to its last saved state. .DESCRIPTION Undo-Changes is the GitEasy-first way to abandon every unsaved edit. Because the operation is destructive, the command refuses to run without explicit confirmation: pass -Force, or accept the standard PowerShell -Confirm prompt. For a softer alternative, Save-Work -NoPush will save the current state locally first, so you can recover later if you change your mind. 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 Force Skip the confirmation prompt and discard unsaved changes immediately. .PARAMETER LogPath Override the directory where the diagnostic log for this run is written. .EXAMPLE Find-CodeChange; Undo-Changes -Force .EXAMPLE Save-Work 'checkpoint before undo' -NoPush; Undo-Changes -Force .NOTES Safety: - Always run Find-CodeChange first to see what will be discarded. - Use Save-Work -NoPush for a recoverable checkpoint before undoing. - Refuses to run during an unfinished merge, rebase, cherry-pick, revert, or bisect. - Refuses to run while there are unfinished conflicts. Steps: 1. Probe for the project folder root and start a diagnostic log session. 2. Check that no merge, rebase, or other in-progress operation is underway. 3. Read the workspace state; if there is nothing to undo, report "Nothing to undo" and return. 4. Require -Force, -Confirm, or -WhatIf; refuse with a plain-English message if none is provided. 5. Handle ShouldProcess; return early if the user declines or -WhatIf is set. 6. Restore all tracked files to their last saved state. 7. Remove all untracked files and folders. 8. Return a structured result with the count of discarded changes. .LINK Find-CodeChange .LINK Restore-File .LINK Save-Work #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSUseSingularNouns', '', Justification = 'GitEasy uses plain-English plural names as a brand choice; singular form is exported as an alias.' )] [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] [OutputType([PSCustomObject])] [Alias('Undo-Change')] param( [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 'Undo-Changes' -Repository ([string]$repoRoot) -LogPath $LogPath $userMessageOnFailure = 'Could not undo changes.' 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 } $statusResult = Invoke-GEGit -ArgumentList @('status', '--porcelain=v1') -WorkingDirectory $repoRoot -LogPath $session.Path $statusLines = @($statusResult.Output | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) if ($statusLines.Count -eq 0) { Write-Host 'Nothing to undo. The active working area is already clean.' $result = [PSCustomObject]@{ Repository = $repoRoot Discarded = 0 Message = 'Nothing to undo.' } Complete-GELogSession -Path $session.Path -Outcome 'SUCCESS' return $result } $hasExplicitOptIn = $Force -or $PSBoundParameters.ContainsKey('Confirm') -or $PSBoundParameters.ContainsKey('WhatIf') if (-not $hasExplicitOptIn) { throw "Refusing to discard unsaved changes without confirmation. Re-run with -Force to skip the prompt, pass -Confirm to be prompted explicitly, use -WhatIf to preview, or use Save-Work -NoPush first to keep a recoverable checkpoint." } if (-not $Force) { if (-not $PSCmdlet.ShouldProcess($repoRoot, "Discard $($statusLines.Count) unsaved change(s) - this cannot be reversed")) { Complete-GELogSession -Path $session.Path -Outcome 'SUCCESS' -UserMessage 'Skipped (Confirm declined or WhatIf).' return } } Invoke-GEGit -ArgumentList @('checkout', '--', '.') -WorkingDirectory $repoRoot -LogPath $session.Path | Out-Null Invoke-GEGit -ArgumentList @('clean', '-fd') -WorkingDirectory $repoRoot -LogPath $session.Path | Out-Null Write-Host "Discarded $($statusLines.Count) unsaved change(s)." $result = [PSCustomObject]@{ Repository = $repoRoot Discarded = $statusLines.Count Message = "Discarded $($statusLines.Count) unsaved change(s)." } 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)" } } |