Remove Commit History
Intuition
Section titled “Intuition”Removing commit history is a destructive operation that creates a fresh repository with a single commit containing the current state of all files. This is useful when sensitive data like passwords or API keys have been accidentally committed, or when a repository’s history is too cluttered to be useful. The orphan branch technique creates a new branch with no parent, and force-pushing replaces the remote history entirely. The key insight is that Git stores content by hash, so rewriting history creates entirely new commit objects.
Proposed Solution
Section titled “Proposed Solution”- Clone Git repository
- Create orphan branch
git checkout --orphan temp- Stage all changes
git add -Agit commit -m "init commit (cleaned history)"- Delete old branch
git branch -D main- Rename current temp branch to main
git branch -m main- Force-push current branch to GitHub
git push -f origin main- Delete all other branches and tags if needed
Terminal window git tag | xargs git tag -d # Delete local tagsgit push origin --delete --tags # Delete remote tagsgit push origin --delete old-branch # Repeat for historical branches
Common Pitfalls
Section titled “Common Pitfalls”Neglecting to normalise database designs, leading to data redundancy and update anomalies.
Misunderstanding the difference between a stack (LIFO) and a queue (FIFO) in data structure applications.
Forgetting that average-case for quicksort becomes worst-case on already sorted input.
Mixing up Big O, Big , and Big notation. Big O is an upper bound, not necessarily tight.
When to Use This Approach
Section titled “When to Use This Approach”- Removing sensitive data: passwords, API keys, or private keys accidentally committed to the repository history.
- Starting fresh: when a repository”s history is cluttered with merge conflicts, broken commits, or irrelevant experimental branches.
- Reducing repository size: large binary files bloating the
.gitdirectory.
Alternative: git filter-branch
Section titled “Alternative: git filter-branch”For selective history rewriting (removing specific files without losing all history):
## Remove a file from all commitsgit filter-branch --force --index-filter 'git rm --cached --ignore-unmatch path/to/sensitive-file' --prune-empty -- --all
## Clean up and force-pushgit reflog expire --expire=now --allgit gc --prune=now --aggressivegit push --force --allAlternative: BFG Repo-Cleaner
Section titled “Alternative: BFG Repo-Cleaner”For faster history cleaning on large repositories:
# Install BFG# https://rtyley.github.io/bfg-repo-cleaner/
# Clone a fresh bare copygit clone --mirror git@github.com:user/repo.git
# Remove files larger than 10MBjava -jar bfg.jar --strip-blobs-bigger-than 10M repo.git
# Remove a specific filejava -jar bfg.jar --delete-sensitive-file config/secrets.yaml repo.git
# Clean upcd repo.gitgit reflog expire --expire=now --allgit gc --prune=now --aggressivegit pushWarning
Section titled “Warning”These operations are destructive. Anyone who has cloned or forked the repository will have mismatched histories. Coordinate with all contributors before rewriting history. Force-pushing to shared branches should only be done during agreed maintenance windows.
Recovery After Accidental Force-Push
Section titled “Recovery After Accidental Force-Push”If commits are lost after a force-push, several recovery paths exist:
- From local reflog: If the original commits still exist locally,
git refloglists previous HEAD positions. Reset to the desired entry and re-push:Terminal window git refloggit reset --hard HEAD@{1}git push --force - From a collaborator’s clone: Another contributor who has not rebased can push the original history back:
Terminal window git fetch https://github.com/collaborator/repo.git maingit reset --hard FETCH_HEADgit push --force - From GitHub dangling refs: GitHub retains unreachable objects for approximately 30 days. The GitHub API or the
git fsck --unreachablecommand on a fresh clone can sometimes recover lost commits.
Soft Reset Alternative
Section titled “Soft Reset Alternative”Instead of creating an orphan branch, a softer approach collapses all history into one commit while preserving the full working tree state:
git reset --soft $(git rev-list --max-parents=0 HEAD)git commit --amend -m "Squashed initial commit"git push --force origin mainThis avoids orphan branch bookkeeping and keeps the final diff intact in a single commit.
Notes on Shared Repositories
Section titled “Notes on Shared Repositories”Force-pushing to shared branches disrupts all collaborators. Before rewriting history:
- Announce a maintenance window and coordinate with all contributors.
- Ask collaborators to rebase their work onto the new history afterward.
- For truly sensitive data (credentials, tokens), consider rotating the leaked secrets entirely rather than relying solely on history removal, since forks and clones may retain the data.
Summary
Section titled “Summary”The key principles covered in this topic are linked in the sub-pages above. Focus on understanding the definitions, applying the formulas or frameworks, and evaluating strengths and limitations of each approach.
Worked Examples
Section titled “Worked Examples”Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.
Cross-References
Section titled “Cross-References”- Filter Repo is the modern recommended tool for the history rewriting operations this guide covers.
- Git Objects explains the object model that must be reconstructed when commit history is removed.
- Reflog shows how to recover from accidental history rewrites using the reference log.