Skip to content

Instantly share code, notes, and snippets.

@Seekatar
Last active July 23, 2023 20:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Seekatar/6fdf37c78e02312863066c8af99539fc to your computer and use it in GitHub Desktop.
Save Seekatar/6fdf37c78e02312863066c8af99539fc to your computer and use it in GitHub Desktop.
PowerShell script to run commands on git repo folders
<#
.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