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.

# If you haven't pushed yet (local only)
# 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

# If you've already pushed and can't reset
# 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.

# 1. Find the commit hash of your deleted branch's tip
$ 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)

# If you haven't pushed yet (local only)
# 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:

# Option 1: Using BFG Repo-Cleaner (faster for large repos)
$ 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:

# Option 1: Abort the merge and start over (if merge not committed)
$ 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

# 1. Identify conflicted files
$ 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

# ALWAYS use --force-with-lease instead of --force
$ 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

# If you haven't switched away yet:
# 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

# During rebase: abort and start over
$ 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.

! [rejected] main -> main (fetch first)
error: failed to push some refs

How to Handle It

# 1. Fetch the remote changes
$ 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

# Always review what you're adding
$ 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

# If not pushed yet
# 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

# Fix the most recent commit message
$ 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.

# Remove already committed files that should be ignored
# 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
You accidentally committed to the main branch instead of creating a feature branch. You haven't pushed yet. What's the safest way to fix this?
  • Create a new branch, reset main to origin/main, switch to new branch
  • Delete the commit with git reset --hard and lose the work
  • Push to main anyway and create a revert commit
  • Use git cherry-pick to copy commits after pushing

Frequently Asked Questions

Can I recover commits after git reset --hard?

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.

What's the difference between git revert and git reset?

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.

How do I undo a merge that already happened?

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.

I committed to the wrong branch and pushed. What now?

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.

How do I remove a file from Git but keep it locally?

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.

Previous: Security Best Practices Next: Advanced Rebase