mirror of
https://github.com/github/spec-kit.git
synced 2026-07-04 04:45:43 +08:00
* fix: suppress CRLF warnings in auto-commit.ps1 (#2253) Replace 2> with 2>&1 redirection and assignment to properly suppress stderr output including CRLF warnings on Windows. Exit code logic preserved for change detection. Fixes #2253 * fix: use SilentlyContinue for CRLF stderr handling, add tests The 2>&1 approach still raises terminating errors under $ErrorActionPreference='Stop'. Instead, temporarily set SilentlyContinue around all native git calls that may emit CRLF warnings to stderr (rev-parse, diff, ls-files, add, commit). Adds 5 pytest tests (TestAutoCommitPowerShellCRLF) that set core.autocrlf=true with LF-ending files. On Windows runners this triggers actual CRLF warnings; on other platforms the tests pass trivially. Fixes #2253 * refactor: address Copilot review feedback - Use 'Continue' instead of 'SilentlyContinue' so error output is still captured in $out for diagnostics on real git failures. - Wrap all three EAP save/restore blocks in try/finally to guarantee restoration even on unexpected exceptions. - Fix CRLF test to commit a tracked LF file first, then modify it, so git diff --quiet HEAD actually inspects the tracked change and triggers the CRLF warning on Windows. * test: assert CRLF warning fires on Windows On Windows, probe git diff stderr before running the script to verify the test setup actually produces the expected CRLF warning. This makes the regression test deterministic on the Windows runner. On non-Windows the probe is skipped (warnings don't fire there). --------- Co-authored-by: Manfred Riem <15701806+mnriem@users.noreply.github.com>
170 lines
5.2 KiB
PowerShell
170 lines
5.2 KiB
PowerShell
#!/usr/bin/env pwsh
|
|
# Git extension: auto-commit.ps1
|
|
# Automatically commit changes after a Spec Kit command completes.
|
|
# Checks per-command config keys in git-config.yml before committing.
|
|
#
|
|
# Usage: auto-commit.ps1 <event_name>
|
|
# e.g.: auto-commit.ps1 after_specify
|
|
param(
|
|
[Parameter(Position = 0, Mandatory = $true)]
|
|
[string]$EventName
|
|
)
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
function Find-ProjectRoot {
|
|
param([string]$StartDir)
|
|
$current = Resolve-Path $StartDir
|
|
while ($true) {
|
|
foreach ($marker in @('.specify', '.git')) {
|
|
if (Test-Path (Join-Path $current $marker)) {
|
|
return $current
|
|
}
|
|
}
|
|
$parent = Split-Path $current -Parent
|
|
if ($parent -eq $current) { return $null }
|
|
$current = $parent
|
|
}
|
|
}
|
|
|
|
$repoRoot = Find-ProjectRoot -StartDir $PSScriptRoot
|
|
if (-not $repoRoot) { $repoRoot = Get-Location }
|
|
Set-Location $repoRoot
|
|
|
|
# Check if git is available
|
|
if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
|
|
Write-Warning "[specify] Warning: Git not found; skipped auto-commit"
|
|
exit 0
|
|
}
|
|
|
|
# Temporarily relax ErrorActionPreference so git stderr warnings
|
|
# (e.g. CRLF notices on Windows) do not become terminating errors.
|
|
$savedEAP = $ErrorActionPreference
|
|
$ErrorActionPreference = 'Continue'
|
|
try {
|
|
git rev-parse --is-inside-work-tree 2>$null | Out-Null
|
|
$isRepo = $LASTEXITCODE -eq 0
|
|
} finally {
|
|
$ErrorActionPreference = $savedEAP
|
|
}
|
|
if (-not $isRepo) {
|
|
Write-Warning "[specify] Warning: Not a Git repository; skipped auto-commit"
|
|
exit 0
|
|
}
|
|
|
|
# Read per-command config from git-config.yml
|
|
$configFile = Join-Path $repoRoot ".specify/extensions/git/git-config.yml"
|
|
$enabled = $false
|
|
$commitMsg = ""
|
|
|
|
if (Test-Path $configFile) {
|
|
# Parse YAML to find auto_commit section
|
|
$inAutoCommit = $false
|
|
$inEvent = $false
|
|
$defaultEnabled = $false
|
|
|
|
foreach ($line in Get-Content $configFile) {
|
|
# Detect auto_commit: section
|
|
if ($line -match '^auto_commit:') {
|
|
$inAutoCommit = $true
|
|
$inEvent = $false
|
|
continue
|
|
}
|
|
|
|
# Exit auto_commit section on next top-level key
|
|
if ($inAutoCommit -and $line -match '^[a-z]') {
|
|
break
|
|
}
|
|
|
|
if ($inAutoCommit) {
|
|
# Check default key
|
|
if ($line -match '^\s+default:\s*(.+)$') {
|
|
$val = $matches[1].Trim().ToLower()
|
|
if ($val -eq 'true') { $defaultEnabled = $true }
|
|
}
|
|
|
|
# Detect our event subsection
|
|
if ($line -match "^\s+${EventName}:") {
|
|
$inEvent = $true
|
|
continue
|
|
}
|
|
|
|
# Inside our event subsection
|
|
if ($inEvent) {
|
|
# Exit on next sibling key (2-space indent, not 4+)
|
|
if ($line -match '^\s{2}[a-z]' -and $line -notmatch '^\s{4}') {
|
|
$inEvent = $false
|
|
continue
|
|
}
|
|
if ($line -match '\s+enabled:\s*(.+)$') {
|
|
$val = $matches[1].Trim().ToLower()
|
|
if ($val -eq 'true') { $enabled = $true }
|
|
if ($val -eq 'false') { $enabled = $false }
|
|
}
|
|
if ($line -match '\s+message:\s*(.+)$') {
|
|
$commitMsg = $matches[1].Trim() -replace '^["'']' -replace '["'']$'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# If event-specific key not found, use default
|
|
if (-not $enabled -and $defaultEnabled) {
|
|
$hasEventKey = Select-String -Path $configFile -Pattern "^\s*${EventName}:" -Quiet
|
|
if (-not $hasEventKey) {
|
|
$enabled = $true
|
|
}
|
|
}
|
|
} else {
|
|
# No config file — auto-commit disabled by default
|
|
exit 0
|
|
}
|
|
|
|
if (-not $enabled) {
|
|
exit 0
|
|
}
|
|
|
|
# Check if there are changes to commit
|
|
# Relax ErrorActionPreference so CRLF warnings on stderr do not terminate.
|
|
$savedEAP = $ErrorActionPreference
|
|
$ErrorActionPreference = 'Continue'
|
|
try {
|
|
git diff --quiet HEAD 2>$null; $d1 = $LASTEXITCODE
|
|
git diff --cached --quiet 2>$null; $d2 = $LASTEXITCODE
|
|
$untracked = git ls-files --others --exclude-standard 2>$null
|
|
} finally {
|
|
$ErrorActionPreference = $savedEAP
|
|
}
|
|
|
|
if ($d1 -eq 0 -and $d2 -eq 0 -and -not $untracked) {
|
|
Write-Host "[specify] No changes to commit after $EventName" -ForegroundColor DarkGray
|
|
exit 0
|
|
}
|
|
|
|
# Derive a human-readable command name from the event
|
|
$commandName = $EventName -replace '^after_', '' -replace '^before_', ''
|
|
$phase = if ($EventName -match '^before_') { 'before' } else { 'after' }
|
|
|
|
# Use custom message if configured, otherwise default
|
|
if (-not $commitMsg) {
|
|
$commitMsg = "[Spec Kit] Auto-commit $phase $commandName"
|
|
}
|
|
|
|
# Stage and commit
|
|
# Relax ErrorActionPreference so CRLF warnings on stderr do not terminate,
|
|
# while still allowing redirected error output to be captured for diagnostics.
|
|
$savedEAP = $ErrorActionPreference
|
|
$ErrorActionPreference = 'Continue'
|
|
try {
|
|
$out = git add . 2>&1 | Out-String
|
|
if ($LASTEXITCODE -ne 0) { throw "git add failed: $out" }
|
|
$out = git commit -q -m $commitMsg 2>&1 | Out-String
|
|
if ($LASTEXITCODE -ne 0) { throw "git commit failed: $out" }
|
|
} catch {
|
|
Write-Warning "[specify] Error: $_"
|
|
exit 1
|
|
} finally {
|
|
$ErrorActionPreference = $savedEAP
|
|
}
|
|
|
|
Write-Host "[OK] Changes committed $phase $commandName"
|