Private/Invoke-GEGit.ps1
|
function Invoke-GEGit { <# .SYNOPSIS Run a Git command and return its exit code and output as separate stdout and stderr arrays. .DESCRIPTION Invoke-GEGit is the engine's single point of contact with Git. It captures stdout and stderr separately so that warnings (for example, LF/CRLF notices) do not poison parsed output. By default it throws when Git exits non-zero; -AllowFailure suppresses that and returns the result for the caller to inspect. Pass -LogPath to append a per-step record (command, exit code, stdout, stderr) to a diagnostic log file. .PARAMETER ArgumentList The Git command and its arguments, as a string array. .PARAMETER WorkingDirectory Where to run the command. Defaults to the current location. .PARAMETER AllowFailure Return the result instead of throwing when the exit code is non-zero. .PARAMETER LogPath Optional path to a diagnostic log file. When set, every call appends a step record. .EXAMPLE $r = Invoke-GEGit -ArgumentList @('rev-parse', '--show-toplevel') .EXAMPLE $r = Invoke-GEGit -ArgumentList @('push') -WorkingDirectory $root -LogPath $session.Path .NOTES Internal. Public commands route every Git call through this helper. .LINK Save-Work #> [CmdletBinding()] param( [Parameter(Mandatory)] [string[]]$ArgumentList, [string]$WorkingDirectory = (Get-Location).Path, [switch]$AllowFailure, [string]$LogPath = '' ) $previousLocation = Get-Location try { Set-Location -LiteralPath $WorkingDirectory $previousActionPreference = $ErrorActionPreference $ErrorActionPreference = 'Continue' $merged = & git @ArgumentList 2>&1 $exitCode = $LASTEXITCODE $ErrorActionPreference = $previousActionPreference } finally { $ErrorActionPreference = 'Stop' Set-Location -LiteralPath $previousLocation } $stdoutLines = New-Object System.Collections.Generic.List[string] $stderrLines = New-Object System.Collections.Generic.List[string] foreach ($entry in @($merged)) { if ($null -eq $entry) { continue } if ($entry -is [System.Management.Automation.ErrorRecord]) { $stderrLines.Add($entry.ToString()) } else { $stdoutLines.Add($entry.ToString()) } } # F-06: an arg to Invoke-GEGit can be a remote URL with embedded # credentials (e.g. `remote set-url origin https://x:tok@host/...`). # Sanitise each arg before it lands in the log step header or the # thrown error message. Format-GESafeUrl is a no-op on args that are # not URL-shaped, so this is safe to apply unconditionally. $safeArgs = @($ArgumentList | ForEach-Object { Format-GESafeUrl -Url $_ }) if (-not [string]::IsNullOrWhiteSpace($LogPath)) { $stepText = 'git ' + ($safeArgs -join ' ') $logLines = New-Object System.Collections.Generic.List[string] foreach ($line in $stdoutLines) { $logLines.Add($line) } foreach ($line in $stderrLines) { $logLines.Add('[stderr] ' + $line) } Add-GELogStep -Path $LogPath -Step $stepText -ExitCode $exitCode -Output $logLines } if (($exitCode -ne 0) -and (-not $AllowFailure)) { $combined = New-Object System.Collections.Generic.List[string] foreach ($line in $stdoutLines) { $combined.Add($line) } foreach ($line in $stderrLines) { $combined.Add($line) } throw ("git " + ($safeArgs -join ' ') + " exited with code $exitCode" + [Environment]::NewLine + ($combined -join [Environment]::NewLine)) } [PSCustomObject]@{ ExitCode = $exitCode Output = @($stdoutLines) Stderr = @($stderrLines) } } |