<# .SYNOPSIS Sincronizare de inceput de sesiune pentru un proiect ROA (flux SVN + git text FoxBin2Prg). .DESCRIPTION Pasii: 1. svn update pe ProjectRoot (include externalul COMUN\). Esec / conflicte -> stop, exit nenul. 2. git_sync.ps1 -ProjectRoot (improspateaza textele .??2, inclusiv COMUN). Esec (exit nenul) -> avertizare, fara commit-uri de sync, exit nenul. 3. Repo proiect (ProjectRoot\.git): pe main comite "sync SVN r" daca sunt schimbari, apoi reconciliaza cu origin si face push conditionat (vezi mai jos). 4. La fel repo COMUN (ProjectRoot\COMUN\.git), cu revizia din svn info pe folderul COMUN. 5. Inchide branch-urile claude/* terminate (git diff main..branch gol) in ambele repo-uri. 6. Sumar final (commit / reconciliere / push per repo + avertismente). Reconcilierea cu origin (pasii 3-4, DUPA commit-ul local de sync - commit-ul intai face tree-ul curat si aduce sub urmarire fisierele noi, deci nu mai apar blocajele clasice de pull peste modificari necomise / fisiere netracked): - git fetch origin; fara remote origin sau fetch esuat (offline) -> avertisment si comportamentul vechi (doar commit local), exit 0. - doar in urma fata de origin/main -> git merge --ff-only origin/main. - divergenta -> git rebase origin/main. Commit-urile de sync al caror continut e deja identic pe origin (alta masina a rulat acelasi svn update + sync + push inaintea noastra) devin goale la rebase si sunt aruncate automat - NU e conflict si nu cere interventie manuala. - conflict REAL la rebase -> git rebase --abort, avertisment, exit nenul la final. - push origin main DOAR daca toate commit-urile ahead de origin/main sunt commit-uri de sync ("sync SVN[ r]" - continut oglindit din SVN, sursa de adevar). Orice commit non-sync nepushed blocheaza push-ul automat (avertisment; push manual dupa review). - push respins (push simultan de pe alta masina) -> reia fetch + reconciliere + push, maxim 3 incercari, apoi avertisment (se reconciliaza la urmatoarea rulare). Exit: nenul la esec svn/git_sync sau conflict real cu origin; avertismentele (offline, push sarit din cauza commit-urilor non-sync, push esuat) lasa exit 0 si apar in sumar. .PARAMETER ProjectRoot Radacina proiectului. Implicit: directorul curent, daca are .git si un .pjx. .PARAMETER GitOnly Sare peste pasii SVN (1) si regenerarea textelor (2): doar commit + reconciliere + push + inchidere branch-uri. Pentru reconciliere rapida cu origin sau pentru teste (test_roa_sync.ps1) - nu cere working copy SVN si nici FoxBin2Prg. #> [CmdletBinding()] param( [string]$ProjectRoot = '', [switch]$GitOnly ) $ErrorActionPreference = 'Stop' # --- deduce ProjectRoot din directorul curent daca nu e dat --- if (-not $ProjectRoot) { $cwd = (Get-Location).Path $hasGit = Test-Path -LiteralPath (Join-Path $cwd '.git') $hasPjx = @(Get-ChildItem -LiteralPath $cwd -Filter '*.pjx' -File -ErrorAction SilentlyContinue).Count -gt 0 if ($hasGit -and $hasPjx) { $ProjectRoot = $cwd } else { Write-Host "EROARE: -ProjectRoot nedat si directorul curent nu pare radacina de proiect ROA (lipseste .git sau .pjx): $cwd" exit 1 } } if (-not (Test-Path -LiteralPath $ProjectRoot -PathType Container)) { Write-Host "EROARE: ProjectRoot nu exista: $ProjectRoot" exit 1 } $ProjectRoot = (Get-Item -LiteralPath $ProjectRoot).FullName $comunRoot = Join-Path $ProjectRoot 'COMUN' $gitSync = Join-Path $PSScriptRoot 'git_sync.ps1' Write-Host "=== roa_sync: ProjectRoot=$ProjectRoot$(if($GitOnly){' (-GitOnly)'}) ===" # --- helper: revizia SVN a unui folder --- function Get-SvnRev([string]$path) { # EAP local Continue: sub Stop, redirectarea 2>$null a stderr-ului nativ arunca in PS 5.1 $ErrorActionPreference = 'Continue' $rev = (& svn info --show-item revision $path 2>$null | Out-String).Trim() if ($LASTEXITCODE -ne 0 -or -not $rev) { return '' } return $rev } # =========================== 1. svn update =========================== $rev = '' Write-Host '' if ($GitOnly) { Write-Host "--- 1. svn update: sarit (-GitOnly) ---" } else { Write-Host "--- 1. svn update ---" $svnOut = & svn update $ProjectRoot $svnExit = $LASTEXITCODE $svnOut | ForEach-Object { Write-Host " $_" } if ($svnExit -ne 0) { Write-Host "EROARE: svn update a esuat (exit $svnExit). Ma opresc." exit 1 } # svn poate iesi 0 dar raporta conflicte in sumar if ($svnOut | Where-Object { $_ -match 'Summary of conflicts|conflict' }) { Write-Host "EROARE: svn update raporteaza conflicte. Rezolva-le manual, apoi reia. Ma opresc." exit 1 } $rev = Get-SvnRev $ProjectRoot Write-Host " Revizie SVN ProjectRoot: $(if($rev){$rev}else{'necunoscuta'})" } # =========================== 2. git_sync =========================== Write-Host '' if ($GitOnly) { Write-Host "--- 2. git_sync: sarit (-GitOnly) ---" } else { Write-Host "--- 2. git_sync (texte .??2) ---" if (-not (Test-Path -LiteralPath $gitSync)) { Write-Host "EROARE: nu gasesc git_sync.ps1 la $gitSync. Ma opresc." exit 1 } & powershell -ExecutionPolicy Bypass -NoProfile -File $gitSync -ProjectRoot $ProjectRoot $syncExit = $LASTEXITCODE if ($syncExit -ne 0) { Write-Host '' Write-Host "AVERTISMENT: git_sync a raportat esecuri (exit $syncExit). NU fac commit-uri de sync. Ma opresc." exit 1 } } # =========================== 3-4. commit sync + reconciliere origin per repo =========================== # Pe main comite schimbarile ca "sync SVN r", apoi fetch + ff/rebase fata de origin/main # (sync-urile cu continut deja identic pe origin devin goale la rebase si dispar) si push # DOAR daca tot ahead-ul e din commit-uri de sync. Intoarce starea pasilor. function Sync-Repo { param([string]$RepoDir, [string]$Rev, [string]$Label) # EAP local Continue: git scrie normal pe stderr (fetch/push/rebase), iar sub Stop # redirectarea 2>$null ar arunca NativeCommandError in PS 5.1; verificam $LASTEXITCODE. $ErrorActionPreference = 'Continue' $r = [PSCustomObject]@{ Commit='nimic'; Origin='-'; Push='-'; Warnings=@() } if (-not (Test-Path -LiteralPath (Join-Path $RepoDir '.git'))) { Write-Host " ${Label}: fara repo git ($RepoDir) - sar peste." $r.Commit = 'fara-repo' return $r } $branch = (& git -C $RepoDir rev-parse --abbrev-ref HEAD).Trim() if ($branch -ne 'main') { # tree curat = putem trece pe main; altfel nu atingem branch-ul $dirty = @(& git -C $RepoDir status --porcelain).Count -gt 0 if ($dirty) { Write-Host " ${Label}: esti pe '$branch'; sync-ul se face pe main. Ai modificari necomise - sar peste." $r.Commit = 'skip-branch' return $r } Write-Host " ${Label}: pe '$branch' cu tree curat - fac checkout main." & git -C $RepoDir checkout main | Out-Null if ($LASTEXITCODE -ne 0) { Write-Host " ${Label}: checkout main a esuat - sar peste." $r.Commit = 'skip-branch' return $r } } & git -C $RepoDir add -A | Out-Null $staged = @(& git -C $RepoDir status --porcelain).Count -gt 0 if ($staged) { $msg = if ($Rev) { "sync SVN r$Rev" } else { "sync SVN" } & git -C $RepoDir commit -m $msg | Out-Null if ($LASTEXITCODE -ne 0) { Write-Host " ${Label}: commit a esuat - sar peste reconciliere." $r.Commit = 'esec-commit' $r.Warnings += "${Label}: commit de sync esuat" return $r } Write-Host " ${Label}: commit '$msg'." $r.Commit = 'commit' } else { Write-Host " ${Label}: nimic de comis." } # --- reconciliere cu origin (dupa commit: tree curat, fisierele noi sunt deja urmarite) --- $originUrl = (& git -C $RepoDir remote get-url origin 2>$null | Out-String).Trim() if ($LASTEXITCODE -ne 0 -or -not $originUrl) { Write-Host " ${Label}: fara remote origin - repo doar local, sar peste reconciliere." $r.Origin = 'fara-origin' return $r } & git -C $RepoDir fetch origin 2>$null | Out-Null if ($LASTEXITCODE -ne 0) { Write-Host " ${Label}: AVERTISMENT: git fetch origin a esuat (offline / gitea inaccesibil?); reconcilierea ramane pe urmatoarea rulare." $r.Origin = 'fetch-esuat' $r.Warnings += "${Label}: fetch origin esuat - reconciliere amanata" return $r } & git -C $RepoDir rev-parse --verify --quiet origin/main 2>$null | Out-Null if ($LASTEXITCODE -ne 0) { Write-Host " ${Label}: origin nu are inca main - sar peste reconciliere." $r.Origin = 'fara-origin-main' return $r } for ($attempt = 1; $attempt -le 3; $attempt++) { $behind = [int]((& git -C $RepoDir rev-list --count main..origin/main | Out-String).Trim()) $ahead = [int]((& git -C $RepoDir rev-list --count origin/main..main | Out-String).Trim()) if ($behind -gt 0 -and $ahead -eq 0) { & git -C $RepoDir merge --ff-only origin/main 2>$null | Out-Null if ($LASTEXITCODE -ne 0) { Write-Host " ${Label}: AVERTISMENT: fast-forward la origin/main a esuat neasteptat - verifica manual." $r.Origin = 'conflict' $r.Warnings += "${Label}: fast-forward esuat - verifica manual repo-ul" return $r } Write-Host " ${Label}: adus la zi cu origin/main (fast-forward, $behind commit-uri)." $r.Origin = 'ff' } elseif ($behind -gt 0 -and $ahead -gt 0) { Write-Host " ${Label}: local si origin au divergat (ahead $ahead / behind $behind) - rebase pe origin/main." & git -C $RepoDir rebase --empty=drop origin/main 2>$null | Out-Null if ($LASTEXITCODE -ne 0) { & git -C $RepoDir rebase --abort 2>$null | Out-Null Write-Host " ${Label}: CONFLICT real la rebase pe origin/main - rebase anulat. Rezolva manual: git -C $RepoDir rebase origin/main" $r.Origin = 'conflict' $r.Warnings += "${Label}: conflict real cu origin/main - reconciliere manuala necesara" return $r } $r.Origin = 'rebase' } elseif ($r.Origin -eq '-') { $r.Origin = if ($ahead -gt 0) { 'doar-ahead' } else { 'la-zi' } } # --- push doar daca tot ahead-ul e din commit-uri de sync --- $aheadMsgs = @(& git -C $RepoDir log --format=%s origin/main..main) | Where-Object { $_ -and $_.Trim() } if ($aheadMsgs.Count -eq 0) { Write-Host " ${Label}: la zi cu origin/main; nimic de impins." $r.Push = 'nimic' return $r } $nonSync = @($aheadMsgs | Where-Object { $_ -notmatch '^sync SVN( r\S+)?$' }) if ($nonSync.Count -gt 0) { Write-Host " ${Label}: AVERTISMENT: ahead de origin include commit-uri non-sync - NU fac push automat (push manual dupa review):" $nonSync | Select-Object -First 5 | ForEach-Object { Write-Host " - $_" } $r.Push = 'sarit-non-sync' $r.Warnings += "${Label}: push sarit - exista commit-uri non-sync nepushed" return $r } & git -C $RepoDir push origin main 2>$null | Out-Null if ($LASTEXITCODE -eq 0) { Write-Host " ${Label}: push origin main ($($aheadMsgs.Count) commit-uri de sync)." $r.Push = 'push' return $r } if ($attempt -lt 3) { Write-Host " ${Label}: push respins (probabil push simultan de pe alta masina) - refetch si reiau (incercarea $($attempt+1))." & git -C $RepoDir fetch origin 2>$null | Out-Null if ($LASTEXITCODE -ne 0) { Write-Host " ${Label}: AVERTISMENT: refetch esuat dupa push respins." $r.Push = 'esec' $r.Warnings += "${Label}: push esuat si refetch esuat - reconciliere la urmatoarea rulare" return $r } } } Write-Host " ${Label}: AVERTISMENT: push respins de 3 ori - renunt; se reconciliaza la urmatoarea rulare." $r.Push = 'esec' $r.Warnings += "${Label}: push esuat dupa 3 incercari" return $r } Write-Host '' Write-Host "--- 3. sync repo proiect ---" $resProj = Sync-Repo -RepoDir $ProjectRoot -Rev $rev -Label 'proiect' Write-Host '' Write-Host "--- 4. sync repo COMUN ---" $revComun = '' $resComun = [PSCustomObject]@{ Commit='nimic'; Origin='-'; Push='-'; Warnings=@() } if (Test-Path -LiteralPath $comunRoot -PathType Container) { if (-not $GitOnly) { $revComun = Get-SvnRev $comunRoot } $resComun = Sync-Repo -RepoDir $comunRoot -Rev $revComun -Label 'COMUN' } else { Write-Host " COMUN: folderul nu exista ($comunRoot) - sar peste." } # =========================== 5. inchidere branch-uri claude/* =========================== # Inchide branch-urile claude/* al caror diff fata de main e gol (munca lor a intrat prin sync). function Close-ClaudeBranches { param([string]$RepoDir, [string]$Label) $closed = @(); $active = @() if (-not (Test-Path -LiteralPath (Join-Path $RepoDir '.git'))) { return @{ Closed=$closed; Active=$active } } $branches = @(& git -C $RepoDir for-each-ref --format='%(refname:short)' refs/heads/claude 2>$null) | Where-Object { $_ -and $_.Trim() } | ForEach-Object { $_.Trim() } foreach ($b in $branches) { $diff = @(& git -C $RepoDir diff --name-only main..$b 2>$null) | Where-Object { $_ -and $_.Trim() } if ($diff.Count -eq 0) { & git -C $RepoDir branch -D $b | Out-Null if ($LASTEXITCODE -eq 0) { $closed += $b; Write-Host " ${Label}: inchis $b" } else { Write-Host " ${Label}: nu am putut sterge $b" } } else { $active += $b Write-Host " ${Label}: activ $b ($($diff.Count) fisiere diferite)" } } return @{ Closed=$closed; Active=$active } } Write-Host '' Write-Host "--- 5. inchidere branch-uri claude/* ---" $brProj = Close-ClaudeBranches -RepoDir $ProjectRoot -Label 'proiect' $brComun = @{ Closed=@(); Active=@() } if (Test-Path -LiteralPath $comunRoot -PathType Container) { $brComun = Close-ClaudeBranches -RepoDir $comunRoot -Label 'COMUN' } # =========================== 6. sumar =========================== $commits = @() if ($resProj.Commit -eq 'commit') { $commits += "proiect: sync SVN$(if($rev){" r$rev"})" } if ($resComun.Commit -eq 'commit') { $commits += "COMUN: sync SVN$(if($revComun){" r$revComun"})" } $closedAll = @($brProj.Closed + $brComun.Closed) $activeAll = @($brProj.Active + $brComun.Active) $warnAll = @($resProj.Warnings + $resComun.Warnings) Write-Host '' Write-Host "===================== SUMAR =====================" Write-Host "Revizie SVN: proiect=$(if($rev){$rev}else{'?'})$(if($revComun){" COMUN=$revComun"})" if ($commits.Count -gt 0) { Write-Host "Commit-uri de sync:" $commits | ForEach-Object { Write-Host " $_" } } else { Write-Host "Commit-uri de sync: nimic de sincronizat" } Write-Host "Origin: proiect=$($resProj.Origin)/push=$($resProj.Push) COMUN=$($resComun.Origin)/push=$($resComun.Push)" Write-Host "Branch-uri inchise: $(if($closedAll.Count){$closedAll -join ', '}else{'niciunul'})" Write-Host "Branch-uri active: $(if($activeAll.Count){$activeAll -join ', '}else{'niciunul'})" if ($warnAll.Count -gt 0) { Write-Host "AVERTISMENTE:" $warnAll | ForEach-Object { Write-Host " $_" } } Write-Host "================================================" if (($resProj.Origin -eq 'conflict') -or ($resComun.Origin -eq 'conflict')) { exit 1 } exit 0