# rolling-integrator.ps1 - ephemeral run $ErrorActionPreference = "Continue" $Repo = "G:\crypto miner" Set-Location $Repo $LogPath = Join-Path $Repo "rolling-integrator.log" $PushLog = [System.Collections.Generic.List[string]]::new() $SuccessPushes = 0 $Start = Get-Date $MaxMinutes = 20 $PollSeconds = 120 $LastFetch = [datetime]::MinValue function Write-Log($msg) { $line = "[{0}] {1}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"), $msg Add-Content -Path $LogPath -Value $line Write-Output $line } function Get-TestCounts { Push-Location (Join-Path $Repo "server") $server = @(go test ./... -list . 2>$null | Select-String '^Test').Count Pop-Location Push-Location (Join-Path $Repo "agent") $agent = @(go test ./... -list . 2>$null | Select-String '^Test').Count Pop-Location return @{ Server = $server; Agent = $agent } } function Update-DocCountsIfDrift { $c = Get-TestCounts $problems = Join-Path $Repo "PROBLEMS.md" $readme = Join-Path $Repo "tests\README.md" $p = Get-Content $problems -Raw $r = Get-Content $readme -Raw $changed = $false if ($p -match 'Go server \*\*(\d+)\*\*, agent \*\*(\d+)\*\*') { if ($matches[1] -ne "$($c.Server)" -or $matches[2] -ne "$($c.Agent)") { $p = $p -replace 'Go server \*\*\d+\*\*, agent \*\*\d+\*\*', ("Go server **{0}**, agent **{1}**" -f $c.Server, $c.Agent) $changed = $true } } if ($r -match 'Go server \*\*(\d+)\*\*') { $oldS = [int]$matches[1] if ($oldS -ne $c.Server) { $r = $r -replace 'Go server \*\*\d+\*\*', ("Go server **{0}**" -f $c.Server) $changed = $true } } if ($r -match 'Go agent \*\*(\d+)\*\*') { if ([int]$matches[1] -ne $c.Agent) { $r = $r -replace 'Go agent \*\*\d+\*\*', ("Go agent **{0}**" -f $c.Agent) $changed = $true } } if ($changed) { Set-Content -Path $problems -Value $p -NoNewline Set-Content -Path $readme -Value $r -NoNewline Write-Log "Updated doc counts: server=$($c.Server) agent=$($c.Agent)" return $true } return $false } function Stage-IntegrationFiles { git add -A git reset --quiet HEAD -- usb/ 2>$null git reset --quiet HEAD -- '*.exe' 2>$null Get-ChildItem -Path (Join-Path $Repo "usb") -Filter "*.exe" -Recurse -ErrorAction SilentlyContinue | ForEach-Object { git reset --quiet HEAD -- $_.FullName.Substring($Repo.Length + 1).Replace('\','/') 2>$null } } function Needs-Integration { param([switch]$SkipFetch) if (-not $SkipFetch) { git fetch origin main 2>&1 | Out-Null $script:LastFetch = Get-Date } $porcelain = git status --porcelain $ahead = [int](git rev-list --count origin/main..HEAD 2>$null) $behind = [int](git rev-list --count HEAD..origin/main 2>$null) return @{ Dirty = [bool]$porcelain Ahead = $ahead Behind = $behind Need = ([bool]$porcelain) -or ($ahead -gt 0) -or ($behind -gt 0) } } function Invoke-Integration { Write-Log "=== Integration cycle start ===" $pull = git pull --rebase origin main 2>&1 Write-Log "pull --rebase: $($pull -join ' | ')" if ($LASTEXITCODE -ne 0) { Write-Log "REBASE/PULL FAILED exit=$LASTEXITCODE" return $false } $testOut = & (Join-Path $Repo "scripts\test-suite.ps1") -SkipE2E 2>&1 $testExit = $LASTEXITCODE $testTail = ($testOut | Select-Object -Last 15) -join "`n" Write-Log "test-suite -SkipE2E exit=$testExit tail: $testTail" if ($testExit -ne 0) { Write-Log "TESTS FAILED - not pushing" return $false } $drift = Update-DocCountsIfDrift Stage-IntegrationFiles $status = git status --porcelain if ($status) { git commit -m "Integrator: land agent fixes and sync doc counts." if ($LASTEXITCODE -ne 0) { Write-Log "COMMIT FAILED"; return $false } Write-Log "Committed integration changes" } elseif ($drift) { git add PROBLEMS.md tests/README.md git commit -m "Integrator: refresh test counts in docs." } $ahead = [int](git rev-list --count origin/main..HEAD 2>$null) if ($ahead -eq 0 -and -not (git status --porcelain)) { Write-Log "Clean and nothing to push" return $null } $push = git push origin main 2>&1 $pushExit = $LASTEXITCODE Write-Log "git push origin main exit=$pushExit : $($push -join ' | ')" if ($pushExit -ne 0) { return $false } $clean = -not (git status --porcelain) if ($clean) { $script:SuccessPushes++ $script:PushLog.Add("PUSH_OK #$SuccessPushes at $(Get-Date -Format o) HEAD=$(git rev-parse --short HEAD)") Write-Log "Successful push #$SuccessPushes clean tree" return $true } Write-Log "Push ok but tree dirty" return $false } "" | Set-Content $LogPath Write-Log "Rolling integrator start (max ${MaxMinutes}m, target 3 pushes)" while (((Get-Date) - $Start).TotalMinutes -lt $MaxMinutes -and $SuccessPushes -lt 3) { $n = Needs-Integration Write-Log ("Poll dirty=$($n.Dirty) ahead=$($n.Ahead) behind=$($n.Behind) successPushes=$SuccessPushes") if ($n.Need) { $r = Invoke-Integration if ($r -eq $true) { Write-Log "Cycle succeeded" } elseif ($r -eq $false) { Write-Log "Cycle failed" } } $elapsed = ((Get-Date) - $Start).TotalMinutes if ($SuccessPushes -ge 3 -or $elapsed -ge $MaxMinutes) { break } $sleep = $PollSeconds Write-Log "Sleep ${sleep}s (elapsed $([math]::Round($elapsed,1))m)" Start-Sleep -Seconds $sleep git fetch origin main 2>&1 | Out-Null $LastFetch = Get-Date } $head = git rev-parse HEAD Write-Log "DONE head=$head successPushes=$SuccessPushes elapsed=$([math]::Round(((Get-Date)-$Start).TotalMinutes,1))m" Write-Log "PUSH_LOG:" $PushLog | ForEach-Object { Write-Log $_ } Write-Output "FINAL_HEAD=$head" Write-Output "SUCCESS_PUSHES=$SuccessPushes" $PushLog | ForEach-Object { Write-Output $_ }