114 lines
6.5 KiB
PowerShell
114 lines
6.5 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Restore local-only WIP (stashes + untracked diffs) that was rescued into the
|
|
recovery bundle's at-risk-work\ folder. Run AFTER the repos + submodules are cloned.
|
|
|
|
.DESCRIPTION
|
|
guru-rmm : each stashN-*.patch is applied to the working tree and then re-stashed,
|
|
faithfully recreating the original `git stash` entries. Patches are
|
|
processed highest-N-first so stash0 ends up on top (stash@{0}), matching
|
|
the original LIFO order. The working tree is left CLEAN (changes live in
|
|
the stash, exactly as before).
|
|
guru-connect : tmp-spec018.diff was an UNTRACKED working file, so it is copied back
|
|
into the repo as-is (not applied). Apply it yourself if/when you want it.
|
|
|
|
Non-destructive and re-runnable. If a patch won't apply cleanly (submodule moved on),
|
|
it is reported and the .patch file is left in place for manual `git apply --3way`.
|
|
|
|
ROBUSTNESS NOTES (why this is not just `git apply <file>`):
|
|
* Patch files may have been written by PowerShell redirection (UTF-16 LE/BE w/ BOM).
|
|
`git apply` only understands UTF-8/ASCII and otherwise reports
|
|
"No valid patches in input". Get-Utf8PatchPath normalizes any encoding to a
|
|
UTF-8 (no BOM) temp copy before applying.
|
|
* git writes progress/errors to stderr; capturing that with `2>&1` while
|
|
$ErrorActionPreference='Stop' turns it into a *terminating* error (PS 5.1
|
|
NativeCommandError) that aborts the whole bootstrap. Invoke-Git captures
|
|
output without that trap and returns the real exit code.
|
|
* If the submodule still has stashes, the WIP almost certainly survived the reset.
|
|
Re-applying would create DUPLICATE stashes, so we skip and report instead.
|
|
|
|
.PARAMETER BundlePath Recovery bundle root (auto-detect F:\ then E:\).
|
|
.PARAMETER ClaudeToolsRoot Default D:\claudetools.
|
|
#>
|
|
[CmdletBinding()]
|
|
param([string]$BundlePath,[string]$ClaudeToolsRoot='D:\claudetools')
|
|
$ErrorActionPreference='Stop'
|
|
|
|
# Read a patch regardless of encoding (UTF-16 LE/BE +/- BOM, UTF-8 +/- BOM) and return
|
|
# the path to a normalized UTF-8 (no BOM) temp copy that `git apply` can parse.
|
|
function Get-Utf8PatchPath($path){
|
|
$bytes = [System.IO.File]::ReadAllBytes($path)
|
|
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) { $text = [System.Text.Encoding]::Unicode.GetString($bytes,2,$bytes.Length-2) }
|
|
elseif ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFE -and $bytes[1] -eq 0xFF) { $text = [System.Text.Encoding]::BigEndianUnicode.GetString($bytes,2,$bytes.Length-2) }
|
|
elseif ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) { $text = [System.Text.Encoding]::UTF8.GetString($bytes,3,$bytes.Length-3) }
|
|
else {
|
|
# No BOM: detect UTF-16 LE without BOM by counting interleaved NUL bytes in the head.
|
|
$nul = 0; $n = [Math]::Min(64,$bytes.Length)
|
|
for ($i=0; $i -lt $n; $i++) { if ($bytes[$i] -eq 0) { $nul++ } }
|
|
if ($nul -gt 8) { $text = [System.Text.Encoding]::Unicode.GetString($bytes) }
|
|
else { $text = [System.Text.Encoding]::UTF8.GetString($bytes) }
|
|
}
|
|
$text = $text -replace "`r`n","`n" # normalize to LF so git apply is happy
|
|
$tmp = [System.IO.Path]::GetTempFileName()
|
|
[System.IO.File]::WriteAllText($tmp, $text, (New-Object System.Text.UTF8Encoding($false)))
|
|
return $tmp
|
|
}
|
|
|
|
# Run git without letting native stderr (under $ErrorActionPreference='Stop') become a
|
|
# terminating error. Returns [pscustomobject]@{ Code; Output }.
|
|
function Invoke-Git([string[]]$GitArgs){
|
|
$old = $ErrorActionPreference; $ErrorActionPreference = 'Continue'
|
|
try { $out = (& git @GitArgs 2>&1 | Out-String); $code = $LASTEXITCODE }
|
|
finally { $ErrorActionPreference = $old }
|
|
[pscustomobject]@{ Code = $code; Output = ($out).Trim() }
|
|
}
|
|
|
|
if (-not $BundlePath) { foreach ($d in 'F:','E:','D:') { if (Test-Path "$d\claudetools-recovery\at-risk-work") { $BundlePath="$d\claudetools-recovery"; break } } }
|
|
$aw = "$BundlePath\at-risk-work"
|
|
if (-not $BundlePath -or -not (Test-Path $aw)) { Write-Host "[INFO] no at-risk-work folder found in bundle - nothing to restore"; return }
|
|
Write-Host "[INFO] restoring at-risk WIP from $aw" -ForegroundColor Cyan
|
|
|
|
function Have-Git($repo){ Test-Path "$repo\.git" }
|
|
|
|
# ---- guru-rmm stashes ----
|
|
$rmm = "$ClaudeToolsRoot\projects\msp-tools\guru-rmm"
|
|
if ((Test-Path "$aw\guru-rmm") -and (Have-Git $rmm)) {
|
|
$existing = (Invoke-Git @('-C',$rmm,'stash','list')).Output
|
|
if ($existing) {
|
|
Write-Host "[SKIP] guru-rmm already has stashes (local WIP survived the reset) - not re-applying to avoid duplicates:" -ForegroundColor Yellow
|
|
Write-Host $existing
|
|
Write-Host " Bundle patches remain in $aw\guru-rmm; apply by hand if you really need them." -ForegroundColor Yellow
|
|
}
|
|
elseif ((Invoke-Git @('-C',$rmm,'status','--porcelain')).Output) {
|
|
Write-Host "[WARN] guru-rmm working tree is dirty; skipping auto-restore to avoid mixing changes. Apply patches in $aw\guru-rmm manually." -ForegroundColor Yellow
|
|
} else {
|
|
# highest N first so stash0 lands at stash@{0}
|
|
$patches = Get-ChildItem "$aw\guru-rmm" -Filter '*.patch' | Sort-Object Name -Descending
|
|
foreach ($p in $patches) {
|
|
$u8 = Get-Utf8PatchPath $p.FullName
|
|
try {
|
|
$chk = Invoke-Git @('-C',$rmm,'apply','--check','--3way',$u8)
|
|
if ($chk.Code -ne 0) { Write-Host "[WARN] won't apply cleanly, left for manual restore: $($p.Name) ($($chk.Output))" -ForegroundColor Yellow; continue }
|
|
Invoke-Git @('-C',$rmm,'apply','--3way',$u8) | Out-Null
|
|
Invoke-Git @('-C',$rmm,'stash','push','-u','-m',"restored WIP: $($p.BaseName)") | Out-Null
|
|
Write-Host "[OK] re-stashed guru-rmm: $($p.BaseName)" -ForegroundColor Green
|
|
} finally { Remove-Item $u8 -Force -ErrorAction SilentlyContinue }
|
|
}
|
|
Write-Host "[INFO] guru-rmm stashes now:" -ForegroundColor Cyan
|
|
Write-Host (Invoke-Git @('-C',$rmm,'stash','list')).Output
|
|
}
|
|
}
|
|
|
|
# ---- guru-connect untracked diff ----
|
|
$gc = "$ClaudeToolsRoot\projects\msp-tools\guru-connect"
|
|
$diff = "$aw\guru-connect\tmp-spec018.diff"
|
|
if ((Test-Path $diff) -and (Test-Path $gc)) {
|
|
if (Test-Path "$gc\tmp-spec018.diff") {
|
|
Write-Host "[SKIP] guru-connect\tmp-spec018.diff already present in repo (survived the reset) - not overwriting." -ForegroundColor Yellow
|
|
} else {
|
|
Copy-Item $diff "$gc\tmp-spec018.diff" -Force
|
|
Write-Host "[OK] guru-connect\tmp-spec018.diff restored (untracked working file - 'git apply --3way tmp-spec018.diff' to apply it)" -ForegroundColor Green
|
|
}
|
|
}
|
|
Write-Host "[DONE] at-risk WIP restore" -ForegroundColor Cyan
|