Skip to content

Git Footguns

Mistakes that lose work, break history, or ruin your team's day.


1. git push --force to main

You rebased main and force-pushed. Every other developer's local main now conflicts. Their in-flight PRs are broken. CI is confused. You've rewritten shared history.

Fix: Never force-push to main/master. Use --force-with-lease on feature branches only. Protect main with branch rules.

Under the hood: --force-with-lease checks that the remote ref hasn't changed since your last fetch. If someone else pushed in the meantime, your force-push is rejected. It's a conditional force-push that prevents overwriting others' work — but it only protects against the specific race condition of concurrent pushes. It does NOT protect against rewriting shared history that others have already pulled.


2. Committing secrets

You committed .env with database credentials, API keys, or tokens. Even if you delete the file and commit the deletion, the secret is in git history forever. Bots scan GitHub for exactly this.

Fix: Add secrets to .gitignore before they're ever committed. Use git-secrets or gitleaks as a pre-commit hook. If you commit a secret, rotate it immediately — don't just delete the file.

War story: Uber's 2016 data breach exposed 57 million user records. Attackers found AWS credentials in a private GitHub repository. The credentials gave access to an S3 bucket containing rider and driver data. Uber paid the attackers $100,000 to delete the data (and was later fined $148 million for failing to disclose the breach). A pre-commit hook checking for AWS key patterns would have prevented the initial exposure.


3. git checkout . when you meant git stash

You wanted to save your changes for later. You typed git checkout . instead of git stash. All your uncommitted work is gone. git stash would have saved it.

Fix: Use git stash to save work temporarily. If you did checkout ., check git fsck --lost-found — sometimes blobs survive.


4. Rebasing public branches

You rebased a branch that three other people are working on. Their copies of the branch now have divergent history. They'll get merge conflicts on every pull, and git pull will create ugly merge commits or duplicated commits.

Fix: Only rebase branches that you alone work on. For shared branches, merge instead.


5. Giant binary files in the repo

You committed a 500MB database dump, a video file, or a build artifact. Now every clone downloads 500MB extra, forever. git filter-branch or BFG Repo-Cleaner can fix it, but it rewrites history.

Fix: Use .gitignore for build artifacts and large files. Use Git LFS for binaries that need versioning. Set up CI to catch large file commits.


6. Merge conflicts in generated files

You have package-lock.json, yarn.lock, or go.sum in the repo (as you should). Two branches modify dependencies. The merge conflict in the lock file is 2000 lines of unreadable JSON. You "resolve" it by picking one side, breaking the other.

Fix: For lock files, resolve by re-running the package manager: accept one side's package.json, delete the lock file, run npm install. Commit the regenerated lock file.


7. git reset --hard without thinking

You ran git reset --hard HEAD~3 to "clean up" but forgot you had uncommitted changes. Those changes are gone — not in reflog, not in stash, just gone.

Fix: Always git stash before destructive operations. If you already lost work, git fsck --lost-found might recover dangling blobs.


8. Amending a published commit

You ran git commit --amend on a commit that's already pushed. Now your local and remote histories diverge. You force-push to fix it, which breaks your coworker's branch.

Fix: Only amend unpushed commits. For pushed commits, create a new commit with the fix.


9. Ignoring .gitignore too late

You added node_modules/ to .gitignore but it was already tracked. Git still tracks it. Every npm install shows 5000 changed files.

Fix: After adding to .gitignore, remove from tracking: git rm -r --cached node_modules/ then commit.


10. Committing to the wrong branch

You spent 2 hours coding on main instead of your feature branch. Now you need to move those commits without losing them.

Fix: git stashgit checkout -b featuregit stash pop. Or git branch feature (create branch at current state) → git reset --hard HEAD~N on main.

Debug clue: git log --all --oneline shows commits on all branches. git reflog shows every HEAD position including commits that no branch points to. If you committed to the wrong branch and already pushed, git cherry-pick <sha> on the correct branch is safer than trying to move commits around with reset.


11. Merge commit soup

Your branch has 50 merge commits from repeatedly running git pull (which defaults to merge). The history is unreadable. Bisect is useless.

Fix: Set git config pull.rebase true. Use git pull --rebase to keep linear history on feature branches.


12. Detached HEAD panic

You checked out a tag or commit SHA and now you're in detached HEAD. You make changes, commit, then checkout a branch. Your commit is "lost" — it's a dangling commit with no branch pointing to it.

Fix: Before leaving detached HEAD, create a branch: git checkout -b my-fix. If you already left, use git reflog to find the commit SHA and create a branch from it.

Remember: Git never deletes unreachable commits immediately. They linger for at least 30 days (the gc.reflogExpire default). git reflog is your safety net — it records every HEAD movement. Even after git reset --hard, git checkout, or switching branches, the old commit SHA is in the reflog. The only way to truly lose work is uncommitted changes + destructive operation, or git gc --prune=now.