# ============================================================ # checkpoint.ps1 # ------------------------------------------------------------ # Wird von checkpoint.cmd aufgerufen. Liest # .checkpoint-pending.txt, haengt einen Eintrag an changelog.md # an (mit aktuellem Timestamp vom lokalen PC), fuehrt einen # Git-Commit aus und loescht die Pending-Datei. # # Robust gegen problematische Eingabedaten: # - Doppelte Anfuehrungszeichen in der Zusammenfassung werden # sauber durchgereicht, weil die Commit-Message via # "git commit -F " uebergeben wird (keine # Shell-Argument-Quoting-Probleme). # - Pipe-Zeichen "|" in der Zusammenfassung werden abgelehnt, # weil sie mit dem Changelog-Format # Timestamp | Session | Summary kollidieren. # - Whitespace und Zeilenumbrueche in der Zusammenfassung # werden zu einem Leerzeichen normalisiert. # - Bei Fehler im Hauptablauf wird der Anhang an changelog.md # und die Index-Stagung von changelog.md zurueckgerollt, # sodass der naechste Lauf nicht doppelt anhaengt. # - Eine verwaiste .git/index.lock wird vor dem Lauf erkannt # und mit klarer Anleitung gemeldet. # ============================================================ $ErrorActionPreference = 'Stop' # --- Pfade ------------------------------------------------- $repoRoot = $PSScriptRoot Set-Location $repoRoot $pendingFile = Join-Path $repoRoot '.checkpoint-pending.txt' $changelogFile = Join-Path $repoRoot 'changelog.md' $gitDir = Join-Path $repoRoot '.git' $indexLock = Join-Path $gitDir 'index.lock' $utf8NoBom = New-Object System.Text.UTF8Encoding $false # --- Pre-flight Checks ------------------------------------- if (-not (Test-Path $pendingFile)) { Write-Error ".checkpoint-pending.txt nicht gefunden." exit 1 } if (-not (Test-Path $changelogFile)) { Write-Error "changelog.md nicht gefunden." exit 1 } if (-not (Test-Path $gitDir)) { Write-Error ".git-Verzeichnis nicht gefunden ($gitDir)." exit 1 } if (Test-Path $indexLock) { Write-Error ".git/index.lock existiert bereits. Eine vorherige Git-Operation hat sich nicht aufgeraeumt. Falls kein Git-Prozess mehr laeuft, manuell loeschen: Remove-Item '$indexLock' -Force" exit 1 } # changelog.md darf vor dem Lauf keine lokalen Aenderungen haben, # damit ein eventueller Rollback eindeutig bleibt. $clStatus = & git status --porcelain -- changelog.md 2>$null if ($LASTEXITCODE -ne 0) { Write-Error "git status fehlgeschlagen (Exit Code $LASTEXITCODE). Liegt das Repo in einem konsistenten Zustand vor?" exit 1 } if ($null -ne $clStatus -and ($clStatus -join '').Trim() -ne '') { Write-Error "changelog.md hat lokale Aenderungen. Bitte erst per Hand committen oder mit 'git checkout -- changelog.md' verwerfen, bevor checkpoint.cmd laeuft." exit 1 } # --- Pending-Datei einlesen -------------------------------- $lines = Get-Content $pendingFile -Encoding UTF8 if ($lines.Count -lt 2) { Write-Error ".checkpoint-pending.txt muss mindestens 2 Zeilen enthalten (Session, Zusammenfassung)." exit 1 } $session = $lines[0].Trim() $summaryLines = $lines | Select-Object -Skip 1 | Where-Object { $_.Trim() -ne '' } $summary = ($summaryLines -join ' ') # Whitespace und Steuerzeichen normalisieren $summary = ($summary -replace '\s+', ' ').Trim() if ([string]::IsNullOrWhiteSpace($session)) { Write-Error "Session-Nummer (Zeile 1) ist leer." exit 1 } if ($session -notmatch '^S\d{2,}$') { Write-Error "Session-Nummer '$session' hat nicht das Format S (z.B. S01, S12)." exit 1 } if ([string]::IsNullOrWhiteSpace($summary)) { Write-Error "Zusammenfassung (Zeile 2+) ist leer." exit 1 } if ($summary -match '\|') { Write-Error "Zusammenfassung enthaelt das Pipe-Zeichen '|', das mit dem Changelog-Format Timestamp | Session | Summary kollidiert. Bitte ersetzen." exit 1 } # --- Timestamp und Eintrag bauen --------------------------- $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm' $entry = "$timestamp | $session | $summary" $commitMsg = "${session}: $summary" Write-Host "" Write-Host "[checkpoint] Neuer Eintrag:" -ForegroundColor Cyan Write-Host " $entry" Write-Host "" # --- Backup fuer Rollback und Temp-Message-Datei ----------- $originalChangelog = [System.IO.File]::ReadAllText($changelogFile, $utf8NoBom) $tempMsgFile = Join-Path ([System.IO.Path]::GetTempPath()) ("checkpoint-msg-" + [guid]::NewGuid().ToString() + ".txt") # --- Hauptablauf ------------------------------------------- $success = $false $errorMsg = $null try { # 1. Sicherstellen, dass changelog.md mit Newline endet if ($originalChangelog.Length -gt 0 -and -not ($originalChangelog.EndsWith("`n"))) { [System.IO.File]::AppendAllText($changelogFile, "`r`n", $utf8NoBom) } # 2. Eintrag anhaengen [System.IO.File]::AppendAllText($changelogFile, $entry + "`r`n", $utf8NoBom) Write-Host "[checkpoint] An changelog.md angehaengt." -ForegroundColor Green # 3. Commit-Message-Datei schreiben (UTF-8 ohne BOM) [System.IO.File]::WriteAllText($tempMsgFile, $commitMsg + "`n", $utf8NoBom) # 4. git add -A & git add -A if ($LASTEXITCODE -ne 0) { throw "git add fehlgeschlagen (Exit Code $LASTEXITCODE)." } # 5. git commit -F (umgeht Shell-Argument-Quoting) & git commit -F $tempMsgFile if ($LASTEXITCODE -ne 0) { throw "git commit fehlgeschlagen (Exit Code $LASTEXITCODE)." } Write-Host "[checkpoint] Commit erstellt: $commitMsg" -ForegroundColor Green $success = $true } catch { $errorMsg = $_.Exception.Message } finally { # Temp-Message-Datei aufraeumen if (Test-Path $tempMsgFile) { Remove-Item $tempMsgFile -Force -ErrorAction SilentlyContinue } } if (-not $success) { Write-Host "" Write-Host "[checkpoint] FEHLER: $errorMsg" -ForegroundColor Red Write-Host "[checkpoint] Rollback wird ausgefuehrt..." -ForegroundColor Yellow # changelog.md auf Original-Stand zurueckschreiben try { [System.IO.File]::WriteAllText($changelogFile, $originalChangelog, $utf8NoBom) Write-Host "[checkpoint] changelog.md auf Original-Stand zurueckgerollt." -ForegroundColor Yellow } catch { Write-Host "[checkpoint] WARNUNG: Rollback von changelog.md fehlgeschlagen ($($_.Exception.Message))." -ForegroundColor Red } # Index-Stagung fuer changelog.md auf HEAD zuruecksetzen & git restore --staged -- changelog.md 2>$null | Out-Null if ($LASTEXITCODE -ne 0) { # Fallback fuer aelteres Git ohne 'restore' & git reset HEAD -- changelog.md 2>$null | Out-Null } Write-Host "[checkpoint] FEHLGESCHLAGEN - .checkpoint-pending.txt bleibt erhalten fuer einen erneuten Lauf." -ForegroundColor Red exit 1 } # --- Pending-Datei loeschen (nicht fatal wenn das fehlschlaegt) --- try { Remove-Item $pendingFile -Force Write-Host "[checkpoint] .checkpoint-pending.txt entfernt." -ForegroundColor Green } catch { Write-Host "[checkpoint] WARNUNG: .checkpoint-pending.txt konnte nicht geloescht werden ($($_.Exception.Message)). Bitte manuell entfernen." -ForegroundColor Yellow }