N
Naveenr.dev
Chapter 10
9 min read2026-08-14
📖 Git SeriesChapter 10 · 11 chapters

Git Troubleshooting: Fixing the Most Common Real-World Problems

The scenarios that don't map cleanly to one earlier chapter — a commit on the wrong branch, a conflict that looks unresolvable, a force-push that broke someone else's day — collected as direct fixes instead of concepts, closing out the series.

Git troubleshooting — fixing real-world problems
Git troubleshooting — fixing real-world problems

Every chapter in this series explained the concept first and the command second — on purpose.

When something's actually broken, nobody wants the concept. They want the fix.

This closing chapter is the opposite structure: real situations, straight to the command, with a pointer back to the earlier chapter if the "why" is worth revisiting later.

"I committed to main instead of a feature branch"

bash
git branch feature/my-work     # 1. create a branch at main's current tip
git reset --hard origin/main   # 2. move main back to where it should be
git switch feature/my-work     # 3. your commit is safe here

How it works: Step 1 captures the current tip (including your commit) before you move main back. Nothing is lost — you've just relabeled which branch the commit belongs to.

  • Safe to use --hard here because you just created a branch pointing at the exact commit you're resetting away from.

"I need to undo my last commit but keep the changes"

bash
git reset --soft HEAD~1

Un-commits, keeps everything staged, ready to re-commit properly.

If you want the changes un-staged too (so you can pick which parts to re-commit):

bash
git reset HEAD~1   # --mixed is the default

See the undo chapter for the full breakdown of reset modes.

"A merge conflict looks unresolvable and I want to start over"

bash
git merge --abort

Backs out of the merge entirely — back to exactly how things were before you ran git merge.

Take a breath, re-read the conflict resolution section, and try again with fresh eyes. Conflicts almost always look worse mid-resolution than they actually are once you understand what each side actually changed.

"I force-pushed and now a teammate's branch is broken"

Step 1: Check if it was a mistake on your end first.

bash
git reflog   # find what the branch looked like before the force-push

If you force-pushed the wrong thing, push back to the correct commit:

bash
git push --force-with-lease origin main  # force-push, guarded by the expected remote state

Step 2: Help your teammate recover.

Their local branch now disagrees with the remote's rewritten history. If they have no unpushed local work:

bash
git fetch origin
git reset --hard origin/main

If they do have local work they want to keep, they should save it first:

bash
git branch backup-before-reset     # save their current state
git fetch origin
git reset --hard origin/main
# then cherry-pick or rebase their backup commits as needed
  • Prevent this in future: Prefer git push --force-with-lease to git push --force. It refuses the update when the remote no longer matches your local tracking state, which catches many accidental overwrites. It is a safeguard, not a guarantee: a background fetch can make that local state misleading.

"I deleted a branch and need it back"

bash
git reflog
# Look for the last commit the branch pointed at
# e.g.: a1b2c3d HEAD@{4}: commit: Add checkout validation

git branch recovered-branch a1b2c3d

The reflog remembers where HEAD was even after the branch label pointing at it is gone. Full explanation in the reflog chapter.

"I need to find which commit introduced a bug"

bash
git bisect start
git bisect bad                         # current commit is broken
git bisect good <known-good-commit>    # this older commit was fine
# Git checks out a commit halfway between — test it
git bisect good    # or: git bisect bad
# repeat ~8 times
git bisect reset   # return to where you started

If you have an automatable test:

bash
git bisect run ./test.sh   # fully automated binary search

See the bisect section for the full walkthrough.

"I want to combine my messy commits before opening a PR"

bash
git rebase -i HEAD~<n>
# In the editor: change "pick" to "fixup" for WIP commits
# Save and close

The interactive rebase chapter covers this in depth, including the safety rule: only squash commits nobody else has already pulled.

"I committed a secret (API key, password) by mistake"

  • This is a real incident. Don't skip steps.

Step 1 — Rotate the credential immediately.

Treat it as compromised the moment it was pushed, regardless of whether the repository is public or private. This step has nothing to do with Git and is the one people skip when they shouldn't.

Step 2 — Remove it from history.

bash
# Install git-filter-repo (recommended tool)
pip install git-filter-repo

# Remove the secret from all history
git filter-repo --path-glob '*.env' --invert-paths
# or target a specific file
git filter-repo --path config/secrets.js --invert-paths

# Force-push the rewritten history
git push --force-with-lease origin main

# Have everyone with a clone re-clone or hard-reset

Older alternatives: git filter-branch (deprecated, slow), BFG Repo-Cleaner.

  • Step 1 is not optional even after Step 2. Anyone who already cloned before the history was rewritten still has the old commit in their local reflog. The key is compromised — rotate it.

"I'm not sure what a command will do and don't want to risk it"

Two habits worth building regardless of which specific problem you're facing:

bash
git status    # understand where you actually are before doing anything
git reflog    # your local undo history — almost always has a way back

And before any destructive command on a commit you care about:

bash
git branch backup-$(date +%Y%m%d)   # cheap insurance policy

Creates a branch pointing at your current HEAD. If the command goes wrong, you can always reset to that branch. Costs nothing if you didn't need it.

Quick reference — common problems at a glance

ProblemCommand
Committed to wrong branchgit branch <new>git reset --hard origin/maingit switch <new>
Undo last commit, keep stagedgit reset --soft HEAD~1
Undo last commit, keep editinggit reset HEAD~1
Conflict looks unresolvablegit merge --abort
Force-push broke someonegit push --force-with-lease (to undo), teammate runs git reset --hard origin/main
Deleted a branchgit refloggit branch <name> <hash>
Find which commit broke somethinggit bisect start / git bisect run
Squash WIP commits before PRgit rebase -i HEAD~<n>
Committed a secretRotate credential + git filter-repo

That's the full arc: the local repository model, branching and merging, rewriting history safely, undoing mistakes, working with remotes, what's actually inside .git, extending Git with hooks/submodules/LFS, team conventions, and now the troubleshooting reference to tie it together.

Every command in this series traces back to the same small set of ideas from the internals chapter — a handful of object types, addressed by content, referenced by pointers that move. Once that model is solid, new Git commands stop being things to memorize and start being things you can reason about from first principles.

Enjoyed this chapter?

Get an email when I publish the next chapter. No spam — just new technical deep-dives.

Comments

Share feedback or questions about this blog post.

No comments yet. Be the first to share your thoughts.