Last active
July 23, 2023 20:01
-
-
Save Seekatar/6fdf37c78e02312863066c8af99539fc to your computer and use it in GitHub Desktop.
PowerShell script to run commands on git repo folders
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.SYNOPSIS | |
Run a scriptblock for all sub folders that are Git repos. Supports -WhatIf, -Confirm | |
.PARAMETER ScriptBlock | |
Scriptblock to run for each git Folder. Defaults to $defaultFegScriptBlock which shows status. See Notes for parameters | |
.PARAMETER HasWorking | |
Set to true to only run on folders that have 'working' git flag set | |
.PARAMETER OnBranch | |
If set will only run on folders that are in branch of OnBranch. Can be a wildcard. | |
.PARAMETER Folder | |
Base folder to scan subfolders in. Defaults to $PWD | |
.PARAMETER ShowNumber | |
If set, adds a Number property to the output in default scriptblock | |
.PARAMETER ShowFolder | |
If set, shows a separator with the folder name before processing it | |
.PARAMETER Include | |
Array of masks to of subfolders to include (e.g. *dotnet*), defaults to * and accepted as pipeline input | |
.PARAMETER StartWith | |
Which folder to start processing on. This is value is logged out in the event of an error so you can restart where you left off. | |
.PARAMETER Depth | |
How many levels of subfolders to scan. Defaults to 1 | |
.NOTES | |
The scriptblock is called with three parameters: | |
$dir - the current folder | |
$gitStatus - the result of Get-GitStatus | |
$number - the number of folders processed so far, used for -StartWith | |
It also has access to all of the parameters | |
.OUTPUTS | |
Whatever the scriptblock outputs | |
.EXAMPLE | |
ForEach-Git -OnBranch 'aws' | |
Show all folders that are in the aws branch | |
.EXAMPLE | |
ForEach-Git -HasWorking | |
Show all folders that have something to commit | |
.EXAMPLE | |
ForEach-Git -ScriptBlock { param($dir,$gitStatus,$number) git co main && git co -B 'pre-aws' && git push -u origin pre-aws && git co main } -ShowNumber | |
Create a new branch from current main and push to remote. Params shown for completeness, but not used. | |
.EXAMPLE | |
ForEach-Git -ScriptBlock { git fetch origin main:main && git co aws && git pull && git rebase main } | |
Update main and rebase aws against it | |
.EXAMPLE | |
"Webhook-Service","Mobile-Gateway" | ForEach-Git -show | |
Just show the status of two folders | |
.EXAMPLE | |
feg -Depth 4 -ShowNumber -Include *dotnet* | ft | |
Go a maximum of 4 levels deep, show the number of folders processed and only process folders with 'dotnet' in the name. | |
.EXAMPLE | |
feg -include PixelBoard -script { git branch -M main && git push -u origin main && gh repo edit --default-branch main && git push origin --delete master } | |
Switch one GitHub repo from master to main branch. You must have the GitHub CLI installed and logged in. | |
.EXAMPLE | |
feg -Exclude samples,other -depth 4 -HasWorking | |
Show all folders that have something to commit, but exclude any that have 'samples' or 'other' in the name | |
#> | |
function ForEach-Git { | |
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "")] # ForEach isn't valid, go figure | |
[CmdletBinding(SupportsShouldProcess)] | |
param( [scriptblock] $ScriptBlock = $defaultFegScriptBlock, | |
[string] $Folder = $PWD, | |
[Parameter(ValueFromPipeline)] | |
[string[]] $Include = '*', | |
[string[]] $Exclude, | |
[switch] $HasWorking, | |
[string] $OnBranch, | |
[switch] $ShowNumber, | |
[switch] $ShowFolder, | |
[int] $StartWith = 0, | |
[int] $Depth = 1 | |
) | |
begin { | |
Set-StrictMode -Version Latest | |
$script:found = 0 | |
if (!(Get-Command Get-GitStatus)) { | |
throw "Posh-Git is required: Install-Module -Name posh-git" | |
} | |
if ($Folder) { | |
if (!(Test-Path $Folder)) { | |
throw "'$Folder' not found" | |
} | |
} | |
$includes = @() | |
} | |
process { | |
Set-StrictMode -Version Latest | |
$includes += $include | |
} | |
end { | |
Set-StrictMode -Version Latest | |
Write-Verbose "Includes is $($includes -join ',')" | |
$color = $PSStyle.Formatting.FormatAccent | |
$reset = $PSStyle.Reset | |
function matchesMasks($kid) | |
{ | |
$ok = $false | |
$name = Split-Path $kid -Leaf | |
foreach ($i in $includes) { | |
if ($name -like $i) { | |
foreach ($x in $Exclude) { | |
if ($name -like $x) { | |
Write-Information "${color}Skipping '$kid' since in excludes$reset" -InformationAction Continue | |
return $false | |
} | |
} | |
$ok = $true | |
break | |
} | |
} | |
return $ok | |
} | |
function processFolder($CurrentFolder, $CurrentDepth = 1) | |
{ | |
$kids = Get-ChildItem -Path $CurrentFolder -Directory | |
foreach ($kid in $kids | Where-Object { matchesMasks $_ $includes } ) { | |
Push-Location $kid | |
$script:currDir = $PWD | |
Write-Verbose "Checking $kid" | |
try { | |
if ((Test-Path .git)) { | |
$gitStatus = (Get-GitStatus) | |
if ((!$HasWorking -or $gitStatus.HasWorking) -and | |
(!$OnBranch -or $gitStatus.Branch -like $OnBranch)) { | |
$script:found += 1 | |
if ($ShowFolder) { | |
Write-Information "`n$color$('-'*50)`n👉 Processing '$($kid.name)' folder #$script:found$reset" -InformationAction:Continue | |
} | |
if (($StartWith -le $script:found) -and $PSCmdlet.ShouldProcess($kid.name, "Execute scriptblock")) { | |
Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList $kid,$gitStatus,$script:found | |
if ($LASTEXITCODE -ne 0) { | |
throw "Non-zero exit code $LASTEXITCODE processing '$kid'" | |
} | |
} | |
} | |
} elseif ($CurrentDepth -lt $Depth) { | |
processFolder $kid $Depth ($CurrentDepth + 1) | |
} | |
} finally { | |
Pop-Location | |
} | |
} | |
} | |
try { | |
processFolder $Folder | |
if ($script:found -eq 0) { | |
Write-Information "No matching git sub folders found in '$Folder'" -InformationAction Continue | |
} | |
} catch { | |
Write-Warning "Error processing '$script:currDir'" | |
Write-Warning "To processing again from there, add '-StartWith $script:found'" | |
Write-Error $_ | |
} | |
} | |
} | |
if (!(Test-Path alias:feg)) { | |
New-Alias feg ForEach-Git | |
} | |
$defaultFegScriptBlock = { param($dir,$gitStatus,$number) | |
$ret = [PSCustomObject]@{ | |
Name = $dir.name | |
Branch = $gitStatus.Branch | |
Working = $gitStatus.HasWorking | |
} | |
if ($ShowNumber) { | |
Add-Member -InputObject $ret -Name 'Number' -MemberType NoteProperty -Value $number | |
} | |
if ($Depth -gt 1) { | |
Add-Member -InputObject $ret -Name 'Parent' -MemberType NoteProperty -Value (Split-Path $dir -Parent).Replace($Folder,'').Trim('\/') | |
} | |
$ret | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment