Adauga scripts/roa_sync.ps1 (mutat din UTIL\foxbin2prg) + testele lui
Sync-ul de sesiune se distribuie de-acum cu COMUN (svn update / comun.git). Fata de versiunea veche: dupa commit-ul local de sync reconciliaza cu origin (fetch + ff-only sau rebase --empty=drop; sync-urile byte-identice cu origin dispar singure) si face push automat DOAR cand tot ahead-ul e din commit-uri de sync; conflict real -> abort + exit nenul. Switch -GitOnly pentru reconciliere rapida / teste. Doc inrolare actualizat (tooling + pasul 6). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AvccPZXr4A9ihcTtExNoJT
This commit is contained in:
@@ -21,8 +21,10 @@ ROAFACTURARE + COMUN (romfast/comun.git). Modelul de referinta este ROACONT.
|
||||
## Preconditii
|
||||
|
||||
1. Tooling-ul partajat exista in `D:\ROA\UTIL\foxbin2prg\`: `git_sync.ps1`,
|
||||
`vfp_filemap.ps1`, `txt2vcx.ps1`, `roa_sync.ps1`, `test_git_sync.ps1`,
|
||||
`vfp_filemap.ps1`, `txt2vcx.ps1`, `test_git_sync.ps1`,
|
||||
`test_roundtrip.ps1`. Citeste-le inainte (macar help-ul din git_sync.ps1).
|
||||
`roa_sync.ps1` (+ `test_roa_sync.ps1`) traieste in `COMUN\scripts\` si vine
|
||||
singur cu svn update; in foxbin2prg a ramas doar un stub de compatibilitate.
|
||||
2. Proiectul e working copy SVN valid (`svn info D:\ROA\<PROIECT>`), cu `COMUN\`
|
||||
inauntru (external SVN).
|
||||
3. Repo-ul gitea exista: `git ls-remote git@gitea.romfast.ro:romfast/<proiect>.git`.
|
||||
@@ -104,10 +106,11 @@ in care s-a lucrat efectiv, dupa svn update.
|
||||
|
||||
### 6. roa_sync.bat
|
||||
|
||||
Copiaza `D:\ROA\ROACONT\roa_sync.bat` in radacina proiectului (e generic, foloseste
|
||||
`%~dp0`) si include-l in commit. Utilizatorul il ruleaza la inceput de sesiune:
|
||||
svn update -> git_sync -> commit "sync SVN rN" pe main (proiect + COMUN) -> inchide
|
||||
branch-urile claude/* cu diff gol.
|
||||
Copiaza `D:\ROA\ROAFACTURARE\roa_sync.bat` in radacina proiectului (e generic,
|
||||
foloseste `%~dp0` si cheama `COMUN\scripts\roa_sync.ps1`) si include-l in commit.
|
||||
Utilizatorul il ruleaza la inceput de sesiune: svn update -> git_sync -> commit
|
||||
"sync SVN rN" pe main (proiect + COMUN) -> reconciliere cu origin + push (doar
|
||||
commit-uri de sync) -> inchide branch-urile claude/* cu diff gol.
|
||||
|
||||
### 7. Verificari finale
|
||||
|
||||
|
||||
338
scripts/roa_sync.ps1
Normal file
338
scripts/roa_sync.ps1
Normal file
@@ -0,0 +1,338 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Sincronizare de inceput de sesiune pentru un proiect ROA (flux SVN + git text FoxBin2Prg).
|
||||
|
||||
.DESCRIPTION
|
||||
Traieste in COMUN\scripts\ si se distribuie automat prin svn update (fiecare proiect isi
|
||||
ruleaza copia lui: <ProjectRoot>\COMUN\scripts\roa_sync.ps1, de regula prin roa_sync.bat
|
||||
din radacina proiectului). Pasul 2 depinde de tooling-ul din D:\ROA\UTIL\foxbin2prg
|
||||
(git_sync.ps1 + FoxBin2Prg.EXE), care ramane acolo.
|
||||
|
||||
Pasii:
|
||||
1. svn update pe ProjectRoot (include externalul COMUN\). Esec / conflicte -> stop, exit nenul.
|
||||
2. git_sync.ps1 -ProjectRoot <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<REV>" 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<rev>]" - 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'
|
||||
# tooling-ul de conversie ramane in clona foxbin2prg (acolo sta si FoxBin2Prg.EXE)
|
||||
$gitSync = 'D:\ROA\UTIL\foxbin2prg\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<rev>", 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
|
||||
239
scripts/test_roa_sync.ps1
Normal file
239
scripts/test_roa_sync.ps1
Normal file
@@ -0,0 +1,239 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Teste standalone (fara Pester) pentru reconcilierea cu origin din roa_sync.ps1, pe
|
||||
fixture-uri git construite in TEMP (origin bare + doua clone "masini"). Ruleaza
|
||||
roa_sync.ps1 -GitOnly (fara SVN, fara FoxBin2Prg), deci nu atinge niciun proiect real.
|
||||
|
||||
Scenarii:
|
||||
(A) scenariul din 2026-07-27: modificari locale necomise + fisier netracked, toate
|
||||
byte-identice cu ce a impins deja alta masina -> reconciliere automata (sync-ul
|
||||
local devine gol la rebase si e aruncat), fara interventie manuala, exit 0;
|
||||
(B) behind + continut local NOU -> rebase pastreaza sync-ul si il impinge pe origin;
|
||||
(C) doar behind, fara modificari locale -> fast-forward la origin/main;
|
||||
(D) conflict real (acelasi fisier, continut diferit) -> rebase --abort, exit nenul,
|
||||
repo lasat curat (fara rebase in curs), commit-ul local pastrat;
|
||||
(E) garda de push: commit non-sync ahead de origin -> push blocat + avertisment;
|
||||
(F) fetch esuat (origin inaccesibil) -> comportamentul vechi (doar commit local), exit 0;
|
||||
(G) push respins o data (hook pre-receive, simuleaza push simultan) -> retry reuseste;
|
||||
(H) repo fara remote origin -> doar commit local, exit 0.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$script:RoaSync = Join-Path $PSScriptRoot 'roa_sync.ps1'
|
||||
$script:Failures = @()
|
||||
|
||||
function Assert([bool]$cond, [string]$msg) {
|
||||
if ($cond) { Write-Host " PASS: $msg" -ForegroundColor Green }
|
||||
else { Write-Host " FAIL: $msg" -ForegroundColor Red; $script:Failures += $msg }
|
||||
}
|
||||
|
||||
# --- git in fixture: EAP Continue local (git scrie pe stderr; sub Stop 2>$null ar arunca) ---
|
||||
function G {
|
||||
param([Parameter(Mandatory)][string]$Repo,
|
||||
[Parameter(Mandatory, ValueFromRemainingArguments)][string[]]$GitArgs)
|
||||
$prev = $ErrorActionPreference; $ErrorActionPreference = 'Continue'
|
||||
$out = (& git -C $Repo @GitArgs 2>$null | Out-String).Trim()
|
||||
$code = $LASTEXITCODE
|
||||
$ErrorActionPreference = $prev
|
||||
if ($code -ne 0) { throw "git -C $Repo $($GitArgs -join ' ') a esuat (exit $code)" }
|
||||
return $out
|
||||
}
|
||||
|
||||
# scriere fisier cu octeti determinati (acelasi continut => aceiasi octeti pe ambele "masini")
|
||||
function WF([string]$path, [string]$content) { [IO.File]::WriteAllText($path, $content) }
|
||||
|
||||
function Set-RepoConfig([string]$repo) {
|
||||
$null = G $repo config user.name 'Test ROA'
|
||||
$null = G $repo config user.email 'test@roa.local'
|
||||
$null = G $repo config commit.gpgsign false
|
||||
$null = G $repo config core.autocrlf false
|
||||
}
|
||||
|
||||
# --- lume de test: origin bare + masinaA (a impins deja) + masinaB (fixture-ul testat) ---
|
||||
function New-World([string]$tag) {
|
||||
$root = Join-Path $env:TEMP ("rs_test_{0}_{1}_{2}" -f $tag, $PID, [Guid]::NewGuid().ToString('N').Substring(0,8))
|
||||
New-Item -ItemType Directory -Force -Path $root | Out-Null
|
||||
$bare = Join-Path $root 'origin.git'
|
||||
$null = G $root init --bare origin.git
|
||||
$null = G $bare symbolic-ref HEAD refs/heads/main
|
||||
# -c la clone seteaza configul in noul repo INAINTE de checkout: cu autocrlf global true,
|
||||
# checkout-ul ar scrie CRLF si un add -A ulterior ar vedea fals toate fisierele modificate
|
||||
$null = G $root clone -c core.autocrlf=false origin.git masinaA
|
||||
$A = Join-Path $root 'masinaA'
|
||||
Set-RepoConfig $A
|
||||
$null = G $A checkout -b main
|
||||
WF (Join-Path $A 'a.prg') "linie 1`nv1`n"
|
||||
WF (Join-Path $A 'b.prg') "b v1`n"
|
||||
$null = G $A add -A
|
||||
$null = G $A commit -m 'initial'
|
||||
$null = G $A push -u origin main
|
||||
$null = G $root clone -c core.autocrlf=false origin.git masinaB
|
||||
$B = Join-Path $root 'masinaB'
|
||||
Set-RepoConfig $B
|
||||
return [PSCustomObject]@{ Root=$root; Bare=$bare; A=$A; B=$B }
|
||||
}
|
||||
|
||||
function Remove-World($w) {
|
||||
Remove-Item -Recurse -Force -LiteralPath $w.Root -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
function Invoke-RoaSync([string]$Fixture) {
|
||||
$prev = $ErrorActionPreference; $ErrorActionPreference = 'Continue'
|
||||
$raw = & powershell -ExecutionPolicy Bypass -NoProfile -File $script:RoaSync -ProjectRoot $Fixture -GitOnly 2>&1 | Out-String
|
||||
$exit = $LASTEXITCODE
|
||||
$ErrorActionPreference = $prev
|
||||
return [PSCustomObject]@{ Exit=$exit; Raw=$raw }
|
||||
}
|
||||
|
||||
# ============================== SCENARIILE ==============================
|
||||
|
||||
function Test-ContinutIdentic {
|
||||
Write-Host "`n--- (A) scenariul 2026-07-27: local byte-identic cu origin (modificat + netracked) ---"
|
||||
$w = New-World 'ident'
|
||||
try {
|
||||
# masina A a facut svn update + sync + push inaintea noastra
|
||||
WF (Join-Path $w.A 'a.prg') "linie 1`nv2`n"
|
||||
WF (Join-Path $w.A 'wincrypt.h') "#define W 1`n"
|
||||
$null = G $w.A add -A; $null = G $w.A commit -m 'sync SVN r100'; $null = G $w.A push origin main
|
||||
# masina B: acelasi svn update + regenerare => aceleasi continuturi, necomise/netracked
|
||||
WF (Join-Path $w.B 'a.prg') "linie 1`nv2`n"
|
||||
WF (Join-Path $w.B 'wincrypt.h') "#define W 1`n"
|
||||
$r = Invoke-RoaSync $w.B
|
||||
Assert ($r.Exit -eq 0) "A: exit 0, fara interventie manuala (exit=$($r.Exit))"
|
||||
Assert ((G $w.B rev-parse main) -eq (G $w.Bare rev-parse main)) "A: main local == origin/main dupa reconciliere"
|
||||
Assert ((G $w.B log -1 --format=%s) -eq 'sync SVN r100') "A: sync-ul local gol a fost aruncat (tip = commit-ul masinii A)"
|
||||
Assert ([string]::IsNullOrWhiteSpace((G $w.B status --porcelain))) "A: working tree curat"
|
||||
} finally { Remove-World $w }
|
||||
}
|
||||
|
||||
function Test-ContinutNou {
|
||||
Write-Host "`n--- (B) behind + continut local nou: rebase pastreaza sync-ul si il impinge ---"
|
||||
$w = New-World 'nou'
|
||||
try {
|
||||
WF (Join-Path $w.A 'a.prg') "linie 1`nv2`n"
|
||||
$null = G $w.A add -A; $null = G $w.A commit -m 'sync SVN r100'; $null = G $w.A push origin main
|
||||
# masina B: aceeasi modificare (identica) + un fisier NOU pe care origin nu il are
|
||||
WF (Join-Path $w.B 'a.prg') "linie 1`nv2`n"
|
||||
WF (Join-Path $w.B 'c.prg') "continut nou local`n"
|
||||
$r = Invoke-RoaSync $w.B
|
||||
Assert ($r.Exit -eq 0) "B: exit 0 (exit=$($r.Exit))"
|
||||
Assert ((G $w.B log -1 --format=%s) -eq 'sync SVN') "B: sync-ul local (continut nou) pastrat dupa rebase"
|
||||
Assert ((G $w.B rev-parse main) -eq (G $w.Bare rev-parse main)) "B: push automat - origin/main actualizat"
|
||||
Assert ((G $w.Bare ls-tree --name-only main) -match 'c\.prg') "B: c.prg a ajuns pe origin"
|
||||
} finally { Remove-World $w }
|
||||
}
|
||||
|
||||
function Test-DoarBehind {
|
||||
Write-Host "`n--- (C) doar behind, fara modificari locale: fast-forward ---"
|
||||
$w = New-World 'ff'
|
||||
try {
|
||||
WF (Join-Path $w.A 'b.prg') "b v2`n"
|
||||
$null = G $w.A add -A; $null = G $w.A commit -m 'sync SVN r101'; $null = G $w.A push origin main
|
||||
$r = Invoke-RoaSync $w.B
|
||||
Assert ($r.Exit -eq 0) "C: exit 0 (exit=$($r.Exit))"
|
||||
Assert ($r.Raw -match 'fast-forward') "C: reconciliere prin fast-forward"
|
||||
Assert ((G $w.B rev-parse main) -eq (G $w.Bare rev-parse main)) "C: main local == origin/main"
|
||||
} finally { Remove-World $w }
|
||||
}
|
||||
|
||||
function Test-ConflictReal {
|
||||
Write-Host "`n--- (D) conflict real: rebase abortat, exit nenul, repo curat ---"
|
||||
$w = New-World 'conf'
|
||||
try {
|
||||
WF (Join-Path $w.A 'a.prg') "linie 1`nvarianta A`n"
|
||||
$null = G $w.A add -A; $null = G $w.A commit -m 'sync SVN r102'; $null = G $w.A push origin main
|
||||
WF (Join-Path $w.B 'a.prg') "linie 1`nvarianta B`n"
|
||||
$r = Invoke-RoaSync $w.B
|
||||
Assert ($r.Exit -ne 0) "D: exit nenul la conflict real (exit=$($r.Exit))"
|
||||
Assert ($r.Raw -match 'CONFLICT') "D: conflictul e raportat explicit"
|
||||
$inRebase = (Test-Path (Join-Path $w.B '.git\rebase-merge')) -or (Test-Path (Join-Path $w.B '.git\rebase-apply'))
|
||||
Assert (-not $inRebase) "D: rebase anulat curat (fara rebase in curs)"
|
||||
Assert ((G $w.B log -1 --format=%s) -eq 'sync SVN') "D: commit-ul local de sync pastrat"
|
||||
Assert ([string]::IsNullOrWhiteSpace((G $w.B status --porcelain))) "D: working tree curat dupa abort"
|
||||
Assert ((G $w.Bare log -1 --format=%s) -eq 'sync SVN r102') "D: origin neatins"
|
||||
} finally { Remove-World $w }
|
||||
}
|
||||
|
||||
function Test-GardaNonSync {
|
||||
Write-Host "`n--- (E) garda de push: commit non-sync ahead blocheaza push-ul automat ---"
|
||||
$w = New-World 'guard'
|
||||
try {
|
||||
$init = G $w.Bare rev-parse main
|
||||
# commit local non-sync (lucrare in curs, nepushed) + modificari de sync necomise
|
||||
WF (Join-Path $w.B 'd.prg') "lucru manual`n"
|
||||
$null = G $w.B add -A; $null = G $w.B commit -m 'lucrare manuala neaprobata'
|
||||
WF (Join-Path $w.B 'b.prg') "b v2`n"
|
||||
$r = Invoke-RoaSync $w.B
|
||||
Assert ($r.Exit -eq 0) "E: exit 0 (exit=$($r.Exit))"
|
||||
Assert ($r.Raw -match 'non-sync') "E: avertisment despre commit-uri non-sync"
|
||||
Assert ((G $w.Bare rev-parse main) -eq $init) "E: origin neatins (fara push)"
|
||||
Assert ((G $w.B log -1 --format=%s) -eq 'sync SVN') "E: sync-ul local totusi comis"
|
||||
} finally { Remove-World $w }
|
||||
}
|
||||
|
||||
function Test-Offline {
|
||||
Write-Host "`n--- (F) fetch esuat (origin inaccesibil): doar commit local, exit 0 ---"
|
||||
$w = New-World 'off'
|
||||
try {
|
||||
$null = G $w.B remote set-url origin (Join-Path $w.Root 'inexistent.git')
|
||||
WF (Join-Path $w.B 'b.prg') "b v3`n"
|
||||
$r = Invoke-RoaSync $w.B
|
||||
Assert ($r.Exit -eq 0) "F: exit 0 (exit=$($r.Exit))"
|
||||
Assert ($r.Raw -match 'fetch origin a esuat') "F: avertisment de fetch esuat"
|
||||
Assert ((G $w.B log -1 --format=%s) -eq 'sync SVN') "F: commit-ul local de sync facut totusi"
|
||||
} finally { Remove-World $w }
|
||||
}
|
||||
|
||||
function Test-PushRetry {
|
||||
Write-Host "`n--- (G) push respins o data (simulare push simultan): retry reuseste ---"
|
||||
$w = New-World 'retry'
|
||||
try {
|
||||
# hook pre-receive: respinge cat timp exista flagul reject_once, apoi accepta
|
||||
$hook = "#!/bin/sh`nif [ -f reject_once ]; then`n rm -f reject_once`n exit 1`nfi`nexit 0`n"
|
||||
[IO.File]::WriteAllText((Join-Path $w.Bare 'hooks\pre-receive'), $hook)
|
||||
WF (Join-Path $w.Bare 'reject_once') "1"
|
||||
WF (Join-Path $w.B 'b.prg') "b v4`n"
|
||||
$r = Invoke-RoaSync $w.B
|
||||
Assert ($r.Exit -eq 0) "G: exit 0 (exit=$($r.Exit))"
|
||||
Assert ($r.Raw -match 'push respins') "G: prima incercare respinsa si raportata"
|
||||
Assert ((G $w.Bare rev-parse main) -eq (G $w.B rev-parse main)) "G: push reusit la reincercare"
|
||||
} finally { Remove-World $w }
|
||||
}
|
||||
|
||||
function Test-FaraOrigin {
|
||||
Write-Host "`n--- (H) repo fara remote origin: doar commit local ---"
|
||||
$w = New-World 'noorig'
|
||||
try {
|
||||
$null = G $w.B remote remove origin
|
||||
WF (Join-Path $w.B 'b.prg') "b v5`n"
|
||||
$r = Invoke-RoaSync $w.B
|
||||
Assert ($r.Exit -eq 0) "H: exit 0 (exit=$($r.Exit))"
|
||||
Assert ($r.Raw -match 'fara remote origin') "H: lipsa origin raportata"
|
||||
Assert ((G $w.B log -1 --format=%s) -eq 'sync SVN') "H: commit-ul local de sync facut"
|
||||
} finally { Remove-World $w }
|
||||
}
|
||||
|
||||
# ============================== MAIN ==============================
|
||||
if (-not (Test-Path -LiteralPath $script:RoaSync)) { throw "Nu gasesc roa_sync.ps1: $script:RoaSync" }
|
||||
|
||||
Write-Host "=== test_roa_sync.ps1 ==="
|
||||
Test-ContinutIdentic
|
||||
Test-ContinutNou
|
||||
Test-DoarBehind
|
||||
Test-ConflictReal
|
||||
Test-GardaNonSync
|
||||
Test-Offline
|
||||
Test-PushRetry
|
||||
Test-FaraOrigin
|
||||
|
||||
Write-Host "`n=== REZULTAT ==="
|
||||
if ($script:Failures.Count -eq 0) {
|
||||
Write-Host "TOATE testele au trecut." -ForegroundColor Green
|
||||
exit 0
|
||||
} else {
|
||||
Write-Host "$($script:Failures.Count) test(e) esuate:" -ForegroundColor Red
|
||||
$script:Failures | ForEach-Object { Write-Host " - $_" -ForegroundColor Red }
|
||||
exit 1
|
||||
}
|
||||
Reference in New Issue
Block a user