Functions/GenXdev.Coding.Git/PermanentlyDeleteGitFolders.ps1
################################################################################ <# .SYNOPSIS Permanently deletes specified folders from all branches in a Git repository. .DESCRIPTION Cleans a Git repository by removing specified folders from the entire commit history across all branches. This is done by cloning the repository, using git filter-branch to remove the folders, and force pushing the changes back. This operation is destructive and permanently rewrites Git history. .PARAMETER RepoUri The Git repository URI to clean (HTTPS or SSH format). .PARAMETER Folders Array of folder paths to remove from the repository history. Paths can be specified with forward or back slashes. .NOTES This operation is destructive and cannot be undone. It rewrites Git history and requires force pushing, which affects all repository users. .EXAMPLE PermanentlyDeleteGitFolders ` -RepoUri "https://github.com/user/repo.git" ` -Folders "bin", "obj" #> function PermanentlyDeleteGitFolders { [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "High")] [OutputType([string[]])] param( ######################################################################## [parameter( Position = 0, Mandatory = $true, HelpMessage = "The URI of the Git repository to clean" )] [string] $RepoUri, ######################################################################## [parameter( Position = 1, Mandatory = $true, HelpMessage = "Array of folder paths to permanently remove" )] [string[]] $Folders ######################################################################## ) begin { # display prominent warnings about the destructive nature of this operation Microsoft.PowerShell.Utility\Write-Warning "!!! DANGER - PERMANENT DESTRUCTIVE OPERATION !!!" Microsoft.PowerShell.Utility\Write-Warning ("This operation will permanently delete the specified " + "folders from ALL git history") Microsoft.PowerShell.Utility\Write-Warning ("It rewrites Git history and force pushes the changes, " + "which CANNOT BE UNDONE") Microsoft.PowerShell.Utility\Write-Warning ("Other users of this repository will need to re-clone " + "or reset their local copies") # create unique temp directory using UTC ticks for isolation $tempPath = GenXdev.FileSystem\Expand-Path ( "$Env:TEMP\$([datetime]::UtcNow.Ticks)" ) -CreateDirectory Microsoft.PowerShell.Utility\Write-Verbose "Using temp directory: $tempPath" # store current location to restore at end Microsoft.PowerShell.Management\Push-Location } process { # final confirmation before proceeding with destructive operation if (-not $PSCmdlet.ShouldProcess( ("Repository: $RepoUri - Permanently delete folders: " + "$($Folders -join ', ')"), ("Are you ABSOLUTELY SURE you want to permanently remove " + "these folders from ALL git history?"), "DANGER: Permanent Git History Modification")) { Microsoft.PowerShell.Utility\Write-Verbose "Operation cancelled by user" return } try { # change to temp directory Microsoft.PowerShell.Management\Set-Location $tempPath Microsoft.PowerShell.Utility\Write-Verbose "Changed to temp directory" # clone the repository Microsoft.PowerShell.Utility\Write-Verbose "Cloning repository: $RepoUri" $null = git clone $RepoUri repo # change to repo directory Microsoft.PowerShell.Management\Set-Location repo Microsoft.PowerShell.Utility\Write-Verbose "Changed to repository directory" # create tracking branches for all remote branches except HEAD Microsoft.PowerShell.Utility\Write-Verbose "Creating tracking branches" git branch -r | Microsoft.PowerShell.Core\ForEach-Object { if (-not $PSItem.Contains("/HEAD")) { $null = git checkout --track $PSItem.Trim() } # process each folder to remove foreach ($folder in $Folders) { # normalize folder path to use forward slashes $folderFixed = $folder.replace("\", "/") if ($folderFixed.endswith("/")) { $folderFixed = $folderFixed.Substring(0, $folderFixed.Length - 1) } if ($PSCmdlet.ShouldProcess($folderFixed, "Removing from Git history")) { # remove folder from git history Microsoft.PowerShell.Utility\Write-Verbose "Removing $folderFixed from history" $filterCommand = "git rm -rf --cached --ignore-unmatch $folderFixed/" git filter-branch --index-filter $filterCommand --prune-empty --tag-name-filter cat -- --all } } try { # clean up refs Microsoft.PowerShell.Utility\Write-Verbose "Cleaning up refs" $refs = git for-each-ref --format="%(refname)" refs/original/ foreach ($ref in $refs) { git update-ref -d $ref } # remove old refs and logs Microsoft.PowerShell.Management\Get-ChildItem @(".git/logs", ".git/refs/original") ` -ErrorAction SilentlyContinue -Directory | Microsoft.PowerShell.Core\ForEach-Object -ErrorAction SilentlyContinue { Microsoft.PowerShell.Management\Remove-Item -LiteralPath $PSItem.FullName -Force -Recurse ` -ErrorAction SilentlyContinue } } catch { Microsoft.PowerShell.Utility\Write-Verbose "Error cleaning up refs (non-critical): $_" } # garbage collect to remove unreferenced commits Microsoft.PowerShell.Utility\Write-Verbose "Running garbage collection" $null = git gc --prune=all --aggressive # force push changes to remote if ($PSCmdlet.ShouldProcess("Origin", "Force pushing all changes")) { Microsoft.PowerShell.Utility\Write-Verbose "Force pushing changes to remote" $null = git push origin --all --force $null = git push origin --tags --force } } } finally { # restore original working directory Microsoft.PowerShell.Management\Pop-Location Microsoft.PowerShell.Utility\Write-Verbose "Restored original location" } } end { } } ################################################################################ |