Common Git Mistakes: How to Avoid and Fix Them
Why Understanding Git Mistakes Matters
Every Git user—from beginners to experts—makes mistakes. The difference between a stressful crisis and a quick recovery is knowing what went wrong and how to fix it. Git's powerful history tracking means most mistakes are recoverable, but only if you understand the right commands and techniques. This comprehensive guide covers the most common Git mistakes, why they happen, and step-by-step recovery methods.
Core principle: Git rarely deletes anything immediately. Commits, branches, and changes often remain in the repository for weeks before garbage collection. The git reflog is your safety net—it records every movement of HEAD and can recover almost any "lost" work.
Mistake #1: Committing to the Wrong Branch
This is perhaps the most common Git mistake. You're working on a hotfix or feature, but forget to switch branches and commit directly to main or develop. This pollutes the main branch with work-in-progress code and complicates the project history.
The Scenario
You've been working on a new feature but forgot to create a feature branch. You make several commits directly to main. Now you need to move those commits to a proper feature branch and reset main to its original state.
How to Fix It
The solution depends on whether you've already pushed the commits to the remote repository.
# 1. Create a new branch from current HEAD (includes the mistaken commits)
$ git branch feature/my-feature
# 2. Reset main back to before your commits
$ git reset --hard origin/main # If you have a remote
# OR reset to a specific commit hash
$ git reset --hard [last-good-commit-hash]
# 3. Switch to your new feature branch and continue working
$ git checkout feature/my-feature
Explanation: The git branch command creates a new branch pointing to your current commit, preserving all your work. Then git reset --hard moves the main branch back to its original position. The commits still exist in the repository and are now only reachable from your new feature branch.
⚠️ Important: If you've already pushed your mistaken commits to the remote repository, you cannot simply reset and force push without coordinating with your team. Force pushing rewrites history and will cause problems for anyone who has pulled the old commits. In that case, consider using git revert instead to create inverse commits that undo the changes while preserving history.
Alternative: Using Cherry-Pick
# 1. Create feature branch from the correct starting point
$ git checkout -b feature/my-feature [base-branch]
# 2. Cherry-pick the commits from main
$ git cherry-pick [commit-hash-1] [commit-hash-2]
# 3. Revert the commits on main
$ git checkout main
$ git revert [commit-hash-1] [commit-hash-2]
$ git push
Mistake #2: Accidentally Deleting a Branch
You run git branch -d feature-branch only to realize you deleted the wrong branch, or the branch hadn't been fully merged. Panic sets in as you think your work is gone forever.
The Scenario
You meant to delete an old, merged branch but accidentally deleted your active feature branch that contains unmerged work. Git may have even warned you with "error: The branch 'feature' is not fully merged." but you used -D (force delete) anyway.
How to Fix It
Git doesn't immediately delete the commits—it just removes the branch reference (pointer). The commits themselves remain in the repository until garbage collection runs (typically 90 days). The git reflog is your best friend here.
$ git reflog
# Output shows all HEAD movements with hashes
a1b2c3d HEAD@{0}: checkout: moving from feature to main
e4f5g6h HEAD@{1}: commit: Add new feature
i7j8k9l HEAD@{2}: commit: WIP on feature
# 2. Look for the last commit on your deleted branch
# In this example, e4f5g6h is likely the tip
# 3. Recreate the branch at that commit
$ git branch feature e4f5g6h
# 4. Verify your work is back
$ git checkout feature
$ git log
Why this works: The reflog records every action that moves HEAD in your local repository. Even after deleting a branch, the commits are still referenced in the reflog until they expire (default 90 days). By finding the commit hash, you can restore the branch exactly where it was.
💡 Pro tip: If you can't find the commit in reflog, try git fsck --lost-found to list dangling commits that aren't referenced by any branch or tag. These are often recoverable branch tips.
Mistake #3: Committing Sensitive Information
You accidentally commit a file containing API keys, passwords, or other secrets. Even if you remove it in a later commit, the secret remains in the Git history forever—or until you purge it.
The Scenario
You added a .env file with production credentials, committed it, and pushed to the remote. Minutes later, you realize the mistake. Bots are already scanning public repositories for exposed secrets.
⚠️ CRITICAL: If you've committed a secret, assume it's compromised immediately. Rotate the secret (change passwords, revoke API keys) BEFORE attempting to remove it from Git. The secret may have been exposed in the brief window it was public.
How to Fix It (If Not Pushed)
# 1. Undo the commit but keep changes staged
$ git reset --soft HEAD~1
# 2. Remove the sensitive file from staging
$ git reset HEAD .env
# 3. Add .env to .gitignore
$ echo ".env" >> .gitignore
$ git add .gitignore
# 4. Commit again without the secret
$ git commit -m "Add project files, ignore .env"
How to Fix It (If Pushed)
Once pushed, you need to purge the secret from history entirely:
$ bfg --delete-files .env your-repo.git
$ git reflog expire --expire=now --all && git gc --aggressive --prune=now
$ git push --force
# Option 2: Using git filter-repo (recommended)
$ git filter-repo --path .env --invert-paths
$ git remote add origin [new-url]
$ git push --force --all
⚠️ IMPORTANT: After force-pushing cleaned history, all collaborators must re-clone or reset their local repositories. Anyone who had the old history will still have the secret in their local clone. Notify your team immediately and ensure everyone fetches the new history with git fetch --all --force and resets.
Mistake #4: Merge Conflicts and Bad Resolutions
Merge conflicts are inevitable, but how you handle them can create new problems. Common mistakes include resolving conflicts incorrectly, committing conflict markers, or losing changes during resolution.
The Scenario
You're merging a feature branch into main. Git reports conflicts. In a hurry, you edit files but accidentally include the conflict markers (<<<<<<<, =======, >>>>>>>) in the final commit, or you choose the wrong version of the changes, losing important work.
How to Fix It
If you've already committed a bad merge resolution, you have several options depending on the situation:
$ git merge --abort
# Option 2: Reset to before the merge (if merge committed but not pushed)
$ git reset --hard ORIG_HEAD
# Option 3: Use git reflog to find pre-merge state
$ git reflog
$ git reset --hard [pre-merge-hash]
# Option 4: If pushed, use git revert on the merge commit
$ git revert -m 1 [merge-commit-hash]
# -m 1 specifies which parent to keep (usually main)
Proper Conflict Resolution Workflow
$ git status
# 2. Open each conflicted file and resolve manually
# Look for markers and choose correct version
# 3. After resolving each file
$ git add [resolved-file]
# 4. Use merge tools for complex conflicts
$ git mergetool # Opens configured merge tool
# 5. Complete the merge
$ git commit # Git will generate merge commit message
Mistake #5: Using --force Instead of --force-with-lease
Force pushing is dangerous. Using git push --force can overwrite remote changes that you haven't seen, causing team members to lose work. --force-with-lease is a safer alternative.
The Scenario
You rebased your branch locally and need to push. You use git push --force without realizing a teammate pushed new commits to the same branch in the meantime. Your force push overwrites their work, which may be lost forever.
How to Prevent and Fix It
$ git push --force-with-lease
# If you accidentally overwrote someone's work:
# 1. Check reflog on the remote (if you have access)
# 2. Ask teammate for their local commits
# 3. Recover using git reflog locally and push again
# Configure Git to make --force-with-lease the default
$ git config --global push.useForceIfIncludes true
How --force-with-lease works: It checks that the remote branch's state matches what you expect (the commit you last fetched). If someone else has pushed, the operation fails, preventing accidental overwrites.
Mistake #6: Detached HEAD State Confusion
You check out a specific commit hash instead of a branch, and Git warns you're in "detached HEAD state." You make commits, then switch away, losing access to those commits.
The Scenario
You run git checkout a1b2c3d to look at an old commit. You make some experimental changes and commit them. Then you switch back to main. Those commits are now "lost" because no branch points to them.
How to Fix It
# Create a branch at your current HEAD
$ git branch new-branch-name
# If you already switched away:
# 1. Find the lost commit in reflog
$ git reflog
b2c3d4e HEAD@{2}: commit: Experimental changes
# 2. Create a branch at that commit
$ git branch recover-experiment b2c3d4e
# 3. Switch to the new branch
$ git checkout recover-experiment
Mistake #7: Messing Up Interactive Rebase
Interactive rebase (git rebase -i) is powerful but dangerous. Common mistakes include squashing the wrong commits, dropping commits unintentionally, or creating conflicts that are hard to resolve.
The Scenario
You start an interactive rebase to clean up commit history. You accidentally mark a commit as "drop" that contained important changes, or you squash commits in the wrong order, losing the ability to track changes properly.
How to Fix It
$ git rebase --abort
# After rebase completed (before push):
# Use reflog to find the commit before rebase
$ git reflog
$ git reset --hard [pre-rebase-hash]
# After push: coordinate with team, then force-with-lease
$ git push --force-with-lease
Mistake #8: Forgetting to Pull Before Pushing
You make commits locally, then try to push, only to be rejected because the remote has new changes. In frustration, you force push or merge incorrectly.
error: failed to push some refs
How to Handle It
$ git fetch origin
# 2. Choose your integration strategy
# Option A: Merge (preserves history)
$ git merge origin/main
# Option B: Rebase (cleaner history)
$ git rebase origin/main
# 3. Resolve any conflicts, then push
$ git push
Mistake #9: Using git add . Without Review
Running git add . adds all changes in the current directory and subdirectories. This can accidentally include temporary files, build artifacts, or even sensitive files you didn't intend to commit.
The Scenario
You're rushing to commit before leaving work. You run git add ., then git commit -m "WIP". Later you realize you committed log files, database dumps, and your IDE configuration—all things that should never be in the repository.
How to Prevent It
$ git status # See what's changed
$ git add -p # Interactive staging - review each change
# Use .gitignore to prevent accidental adds
# Add patterns for temporary files
*.log
*.tmp
.idea/
node_modules/
How to Fix It
# 1. Undo the commit but keep changes
$ git reset --soft HEAD~1
# 2. Unstage the unwanted files
$ git reset HEAD unwanted-file.log
# 3. Add only what you want
$ git add src/
$ git commit -m "Proper commit message"
Mistake #10: Poor Commit Messages
Commit messages like "fix", "update", "asdf", or "WIP" make it impossible to understand project history. This becomes a nightmare when debugging or generating changelogs.
The Scenario
Six months later, you're trying to find when a bug was introduced. Every commit message says "fix" or "update". You have to read through hundreds of commits to find the change you need.
How to Fix It
$ git commit --amend -m "feat(auth): implement OAuth2 login flow"
# Fix older commit messages (interactive rebase)
$ git rebase -i HEAD~5
# Change 'pick' to 'reword' for commits you want to edit
# Best practice: Follow conventional commits format
<type>[optional scope]: <description>
# Types: feat, fix, docs, style, refactor, test, chore
$ git commit -m "feat: add user login endpoint"
$ git commit -m "fix(api): handle null response in user service"
Mistake #11: Ignoring .gitignore
You commit build artifacts, dependencies, or IDE files because you forgot to set up .gitignore properly. This bloats the repository and causes conflicts.
# 1. Add patterns to .gitignore
$ echo "node_modules/" >> .gitignore
$ echo ".env" >> .gitignore
# 2. Remove them from Git (but keep locally)
$ git rm -r --cached node_modules/
$ git rm --cached .env
# 3. Commit the changes
$ git add .gitignore
$ git commit -m "chore: ignore node_modules and env files"
Mistake #12: Confusing fetch, pull, and clone
Many beginners don't understand the difference between git fetch, git pull, and git clone, leading to outdated local repositories or unintended merges.
| Command | What it does | When to use |
|---|---|---|
git clone |
Creates a local copy of a remote repository | First time working with a repository |
git fetch |
Downloads new data from remote without integrating | To see what's changed before merging |
git pull |
Fetch + merge (or rebase) remote changes | To update your current branch with remote changes |
💡 Best practice: Use git fetch followed by git log origin/main to review changes before pulling. This prevents surprise merges and conflicts.
Quick Reference: Common Fixes
| Mistake | Quick Fix |
|---|---|
| Wrong commit message | git commit --amend -m "New message" |
| Forgot to add a file | git add forgotten-file && git commit --amend --no-edit |
| Undo last commit (keep changes) | git reset --soft HEAD~1 |
| Undo last commit (discard changes) | git reset --hard HEAD~1 |
| Unstage a file | git reset HEAD file |
| Discard changes in file | git checkout -- file |
| Recover deleted branch | git reflog then git branch branch-name [hash] |
| Abort ongoing merge/rebase | git merge --abort or git rebase --abort |
Frequently Asked Questions
Yes! git reset --hard moves the branch pointer but doesn't delete commits. Use git reflog to find the commit hash before the reset, then git reset --hard [hash] to return. The reflog keeps entries for 90 days by default. If you've run garbage collection, recovery becomes much harder but may still be possible with git fsck.
git revert creates a new commit that undoes a previous commit. It's safe for shared branches because it doesn't rewrite history. git reset moves the branch pointer backward, removing commits from history. Use reset only for local, unpublished changes. Use revert for commits that have been pushed and shared with others.
If the merge hasn't been pushed, use git reset --hard ORIG_HEAD to return to pre-merge state. If pushed, use git revert -m 1 [merge-commit] to create a revert commit. The -m 1 specifies which parent to keep (usually main). This preserves history while undoing the merge's effects.
First, don't panic. Create the correct branch from your commits: git branch feature-branch. Then revert the commits on main: git revert [commit-hashes]. Push both branches. This is safe for shared repositories because it doesn't rewrite history. Your team will see the revert commits and the new feature branch.
Use git rm --cached file.txt. This removes the file from Git's tracking but leaves it in your working directory. Add the file to .gitignore to prevent accidentally re-adding it. Commit the change to make it permanent in the repository.