Every developer has a Git horror story. Mine involves force-pushing to main at 2 AM and losing four hours of work from two teammates. They still bring it up at team dinners.
After two years of using Git and watching dozens of developers struggle with it, I've noticed the same mistakes come up again and again. Not obscure edge cases. Basic, everyday mistakes that cause real problems.
Here are the ones I see most often, and how to avoid them.
Force Pushing Without Understanding It
git push --force is the nuclear option. It overwrites the remote branch with your local version. If someone else pushed commits after your last pull, those commits are gone.
The safer version is git push --force-with-lease. It checks that the remote branch is where you last fetched it. If someone else pushed in the meantime, it refuses. This one flag has saved me from at least three incidents.
But the real fix is understanding why you need force push in the first place. If you're force pushing because you rebased, that's normal. If you're force pushing because you messed up and want to hide it, stop. Make a revert commit instead. It's honest and it preserves history.
Using git add . Without Looking
I see this constantly. Developers stage everything with git add . and then commit. They don't check what's being staged. This is how API keys, debug logs, temporary files, and personal configs end up in commits.
The fix is simple. Use git add -p (patch mode). It shows you each change and asks if you want to stage it. Yes, it takes longer. But it prevents embarrassing mistakes.
A developer I worked with once committed his AWS access key this way. It was in a .env file he forgot to gitignore. The key was active for six hours before anyone noticed. AWS bills were... educational.
Never Cleaning Up Branches
Open any team's Git repository and run git branch -r | wc -l. I bet the number is way higher than it should be.
Feature branches pile up. Nobody deletes them after merging. Six months later you have 150 remote branches and nobody knows which ones are active. Someone accidentally checks out an old branch, makes changes, and creates a confusing merge situation.
After merging a branch, delete it:
git branch -d feature-name
git push origin --delete feature-name
Or set up your Git config to auto-delete remote tracking branches on pull:
git config --global fetch.prune true
This one line prevents branch pollution. Set it and forget it.
Writing Terrible Commit Messages
"fix stuff" is not a commit message. Neither is "WIP", "asdfasdf", or "changes."
A good commit message explains what changed and why. Not how (the diff shows that). Why.
Bad: "Fixed login bug"
Good: "Redirect to dashboard after login instead of showing blank page"
The first one tells me nothing without reading the diff. The second one tells me exactly what changed and what the bug was. Six months from now, when you're debugging a similar issue, the second message actually helps.
A format that works:
Short summary (50 chars or less)
Optional longer explanation if the change is complex.
Wrap at 72 characters. Explain the motivation, not the mechanics.
You don't need to follow Conventional Commits religiously. Just be specific.
Ignoring Merge Conflicts Instead of Understanding Them
When Git says there's a conflict, some developers panic. They close the editor, checkout the other branch, and start over. Or they pick one side of every conflict without reading the other.
Merge conflicts happen when two people change the same lines. Git doesn't know which version is correct. That's your job.
The conflict markers are:
<<<<<<< HEAD
your changes
=======
their changes
>>>>>>> branch-name
Read both versions. Decide what the correct code should be. Sometimes it's your version, sometimes it's theirs, sometimes it's a combination. Remove the markers and keep the right code.
If you're dealing with a big conflict, use a visual merge tool. VS Code has built-in merge conflict highlighting that shows both versions side by side with "Accept Current" and "Accept Incoming" buttons. It's not perfect but it's way better than reading raw markers.
Not Using .gitignore Early Enough
Every project needs a .gitignore file. Create it before your first commit, not after you've already committed node_modules/ and .env.
GitHub has templates for every language and framework. Go to github.com/github/gitignore and grab the one you need. For a Node.js project:
node_modules/
.env
dist/
*.log
.DS_Store
If you've already committed files that should be ignored, adding them to .gitignore won't remove them from the repo. You need to untrack them:
git rm -r --cached node_modules/
git commit -m "Remove node_modules from tracking"
The --cached flag removes them from Git's index without deleting them from disk. This catches people off guard all the time.
Rebasing Public Branches
Rebasing rewrites history. It takes your commits and replays them on top of a different base. The commit hashes change. If you rebase a branch that other people have based work on, you create duplicate commits and confusion.
The rule is simple: never rebase commits that have been pushed and shared with others. Rebase your local feature branch before pushing it. That's fine. Rebasing main or a shared develop branch? Don't.
If you need to incorporate changes from main into your feature branch, merge instead:
git merge main
Yes, it creates a merge commit. That's fine. Merge commits are honest. They show that two lines of work came together at a specific point.
Using Interactive Rebase to Clean History
Before pushing a feature branch, you can clean up your commit history with interactive rebase. This is one of Git's most useful features and one of the least used.
git rebase -i HEAD~5
This opens your last five commits and lets you squash, reorder, edit, or drop them. Made three "fix typo" commits? Squash them into one. Committed something broken in the middle? Reorder so the fix comes first.
The result is a clean, readable history that tells a coherent story. Your teammates will thank you when they're reviewing your PR and don't have to wade through twelve commits that say "wip" and "fix again."
Interactive rebase feels scary the first time. But it's local, so you can't break anything. Make a backup branch if you're nervous: git branch backup-feature. If the rebase goes wrong, git rebase --abort takes you back.
The One Thing That Helps Most
If I had to pick one habit that makes Git easier, it's this: read git status before every commit. It takes two seconds. It shows you what's staged, what's not, and what's untracked.
Those two seconds have caught more mistakes than any other Git habit I have. Unstaged changes I forgot about. Files I didn't mean to modify. Branch names I forgot to switch.
Git is a tool. Like any tool, it works best when you understand what you're doing instead of just hitting buttons and hoping for the best. The mistakes above aren't hard to avoid. They just require paying a little bit of attention.


