Functions/GenXdev.Coding.Git/PermanentlyDeleteGitFolders.ps1
################################################################################ <# .SYNOPSIS Permanently deletes specified folders from all branches in a Git repository. .DESCRIPTION Clones a repository, removes specified folders from all branches and commits, then force pushes the changes back to origin. This permanently removes the folders from Git history. WARNING: This operation is destructive and cannot be undone. It rewrites Git history and requires a force push, which could cause data loss or conflicts for other users. .PARAMETER RepoUri The URI of the Git repository to clean. .PARAMETER Folders Array of folder paths to permanently remove from the repository history. .NOTES This function supports ShouldProcess and has a high confirmation impact level. You will be prompted to confirm before executing the operation. .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 { Write-Warning "!!! DANGER - PERMANENT DESTRUCTIVE OPERATION !!!" Write-Warning "This operation will permanently delete the specified folders from ALL git history" Write-Warning "It rewrites Git history and force pushes the changes, which CANNOT BE UNDONE" Write-Warning "Other users of this repository will need to re-clone or reset their local copies" # create a unique temporary directory using utc ticks $tempPath = GenXdev.FileSystem\Expand-Path "$Env:TEMP\$([datetime]::UtcNow.Ticks)" -CreateDirectory Write-Verbose "Using temp directory: $tempPath" # store current location to restore later Push-Location } process { 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")) { Write-Verbose "Operation cancelled by user" return } try { # change to temp directory Set-Location $tempPath Write-Verbose "Changed to temp directory" # clone the repository Write-Verbose "Cloning repository: $RepoUri" $null = git clone $RepoUri repo # change to repo directory Set-Location repo Write-Verbose "Changed to repository directory" # create tracking branches for all remote branches except HEAD Write-Verbose "Creating tracking branches" git branch -r | 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 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 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 Get-ChildItem @(".git/logs", ".git/refs/original") ` -ErrorAction SilentlyContinue -Directory | ForEach-Object -ErrorAction SilentlyContinue { Remove-Item -LiteralPath $PSItem.FullName -Force -Recurse ` -ErrorAction SilentlyContinue } } catch { Write-Verbose "Error cleaning up refs (non-critical): $_" } # garbage collect to remove unreferenced commits Write-Verbose "Running garbage collection" $null = git gc --prune=all --aggressive # force push changes to remote if ($PSCmdlet.ShouldProcess("Origin", "Force pushing all changes")) { Write-Verbose "Force pushing changes to remote" $null = git push origin --all --force $null = git push origin --tags --force } } } finally { # restore original location Pop-Location Write-Verbose "Restored original location" } } end { } } |