src/Services/RepoDiscoveryService.ps1
|
# RepoDiscoveryService — Escanea un directorio buscando repos git. # Para v3 minimum: scan inmediato (depth 1) del path raíz. class RepoDiscoveryService { # Nota: $Git tipado como [object] y no [GitService]. PS resuelve los tipos en # propiedades/parámetros de class al parse-time del archivo, y otros archivos # con clases NO se ven en ese contexto (gotcha/ps-class-scope). Late binding # vía duck typing. [object] $Git RepoDiscoveryService($git) { $this.Git = $git } # Lista los hijos directos de $rootPath. Cada hijo se evalúa vía GitService. # Devuelve Repo[] (incluye nogit folders). [Repo[]] Discover([string]$rootPath) { if (-not (Test-Path $rootPath)) { return @() } $children = Get-ChildItem -Path $rootPath -Directory -Force -ErrorAction SilentlyContinue if (-not $children) { return @() } $repos = [System.Collections.Generic.List[object]]::new() foreach ($dir in $children) { # Skip dirs ocultas/sistemas if ($dir.Name.StartsWith('.') -or $dir.Name.StartsWith('$')) { continue } $repo = $this.Git.GetRepoState($dir.FullName) if ($repo.Status -eq 'nogit' -and $this.Git.HasGitChildren($dir.FullName)) { $repo.IsContainer = $true } $repos.Add($repo) } return $repos.ToArray() } # Variante shallow: enumera los hijos pero NO ejecuta git status sobre ellos. # Devuelve Repo[] con Status='unloaded'. El caller decide cuándo cargar # cada uno (selectivo en modo Favorites, on-demand en None). Mucho más # rápido en repos grandes con red lenta. [Repo[]] DiscoverShallow([string]$rootPath) { if (-not (Test-Path $rootPath)) { return @() } $children = Get-ChildItem -Path $rootPath -Directory -Force -ErrorAction SilentlyContinue if (-not $children) { return @() } $repos = [System.Collections.Generic.List[object]]::new() foreach ($dir in $children) { if ($dir.Name.StartsWith('.') -or $dir.Name.StartsWith('$')) { continue } $repo = $this.Git.BuildShallowRepo($dir.FullName) $repos.Add($repo) } return $repos.ToArray() } # Variante paralela: enumera + ejecuta git status en threads paralelos. # 8× más rápido que Discover() secuencial en máquinas lentas con muchos # repos. Para N < 5 repos el spinup overhead no compensa — fallback a # secuencial. # # Limitación PS 7: dentro de ForEach-Parallel no se ven las classes # definidas en el script padre. Por eso usamos Util/GitParse.ps1 con la # función standalone Get-RepoStateData que devuelve hashtables, y # mapeamos a [Repo] en el parent runspace. Pasamos el body de la función # como string por $using: y la reconstruimos con [scriptblock]::Create # adentro de cada thread (PS 7 no permite pasar scriptblocks via $using:). [Repo[]] DiscoverParallel([string]$rootPath, [int]$throttleLimit) { if (-not (Test-Path $rootPath)) { return @() } $children = Get-ChildItem -Path $rootPath -Directory -Force -ErrorAction SilentlyContinue if (-not $children) { return @() } # Filtrar dirs sistema/oculto. Se hace una vez en el parent. $candidates = @($children | Where-Object { -not $_.Name.StartsWith('.') -and -not $_.Name.StartsWith('$') }) # Threshold: spinup de runspaces tiene costo. Por debajo de 5 repos # el secuencial gana. Lo medimos en bench, ajustable acá. if ($candidates.Count -lt 5) { return $this.Discover($rootPath) } $paths = @($candidates | ForEach-Object { $_.FullName }) # Cuerpo de la función standalone como string. Get-Command devuelve el # script body sin la firma `function NAME` — perfecto para Create. $fnBody = (Get-Command Get-RepoStateData -ErrorAction Stop).Definition # Run paralelo. ThrottleLimit acota cuántos git.exe corren al mismo # tiempo. 8 es buen default: 4 cores físicos × 2 hyperthreads sat. # Cada hashtable se devuelve al parent. $hashtables = $paths | ForEach-Object -Parallel { $body = $using:fnBody $sb = [scriptblock]::Create($body) & $sb -Path $_ } -ThrottleLimit $throttleLimit # Mapear hashtables → [Repo]. Acá sí podemos usar la clase, estamos # en el parent runspace. $repos = [System.Collections.Generic.List[object]]::new() foreach ($h in $hashtables) { if ($null -eq $h) { continue } $repo = [Repo]::new() $repo.Path = $h.Path $repo.Name = $h.Name $repo.Id = $h.Id $repo.Status = $h.Status $repo.Branch = $h.Branch $repo.IsOnMainBranch = $h.IsOnMainBranch $repo.Ahead = $h.Ahead $repo.Behind = $h.Behind $repo.Modified = $h.Modified $repo.Added = $h.Added $repo.Deleted = $h.Deleted $repo.Untracked = $h.Untracked $repo.StashCount = $h.StashCount if ($null -ne $h.LastCommit) { $repo.LastCommit = [Commit]::new( $h.LastCommit.Hash, $h.LastCommit.Message, $h.LastCommit.Author, $h.LastCommit.Date ) } # Container detection — si nogit y tiene git children, marcar. if ($repo.Status -eq 'nogit' -and $this.Git.HasGitChildren($repo.Path)) { $repo.IsContainer = $true } $repos.Add($repo) } return $repos.ToArray() } [Repo[]] DiscoverParallel([string]$rootPath) { return $this.DiscoverParallel($rootPath, 8) } } |