Introduction: Git Troubleshooting Is a Survival Skill
Every developer, regardless of experience, has had that gut-wrenching moment when a Git command goes wrong. A force push that overwrites a colleague's work. A merge conflict that seems impossible to resolve. A detached HEAD state with no idea how to get back. Git is incredibly powerful, but that power comes with complexity.
This guide covers the 20 most common Git issues that developers face, with clear explanations of why they happen and step-by-step solutions you can apply immediately. Bookmark this page — you'll need it sooner than you think.
Issue 1: Undo the Last Commit
You committed something by mistake — maybe you included a debug file, wrote the wrong commit message, or committed to the wrong branch.
Soft Reset: Keep Changes Staged
## Undo last commit, keep changes staged (ready to commit again)
git reset --soft HEAD~1
## Undo last 3 commits, keep changes staged
git reset --soft HEAD~3
## Check status — your changes are still staged
git status
Mixed Reset: Keep Changes Unstaged (Default)
## Undo last commit, unstage changes (files are modified but not staged)
git reset HEAD~1
## OR explicitly:
git reset --mixed HEAD~1
## You can now review, modify, and re-stage selectively
git diff
git add specific-file.js
git commit -m "Correct commit message"
Hard Reset: Discard Everything
## WARNING: This permanently deletes changes!
## Undo last commit AND discard all changes
git reset --hard HEAD~1
## Reset to a specific commit
git reset --hard abc1234
## Reset to match remote exactly
git reset --hard origin/main
| Reset Type | Commit | Staging Area | Working Directory | Use When |
|---|---|---|---|---|
--soft | Undone | Preserved | Preserved | Rewrite the commit message or combine commits |
--mixed | Undone | Cleared | Preserved | Unstage and re-organize changes |
--hard | Undone | Cleared | Cleared | Completely discard everything |
Issue 2: Detached HEAD State
What happened: You checked out a specific commit, tag, or remote branch without creating a local branch. Git warns you: You are in 'detached HEAD' state.
Solution: Create a Branch from Detached HEAD
## If you're in detached HEAD and want to KEEP your changes:
git checkout -b my-new-branch
## If you want to go back to your previous branch (discard detached work):
git checkout main
## If you already made commits in detached HEAD and forgot to branch:
## Find the commit hash first
git reflog
## Then create a branch pointing to that commit
git branch recover-branch abc1234
git checkout recover-branch
Issue 3: Merge Conflict Resolution
What happened: Two branches modified the same lines in the same file. Git can't auto-merge and marks conflicts.
Step-by-Step Resolution
## Step 1: Attempt the merge
git merge feature-branch
## Step 2: See which files have conflicts
git status
## Output: "both modified: src/app.js"
## Step 3: Open the conflicted file — you'll see markers:
## <<<<<<< HEAD
## const apiUrl = "https://api.production.com";
## =======
## const apiUrl = "https://api.staging.com";
## >>>>>>> feature-branch
## Step 4: Edit the file — remove markers, keep what you want:
## const apiUrl = process.env.API_URL || "https://api.production.com";
## Step 5: Mark as resolved and commit
git add src/app.js
git commit -m "Merge feature-branch: resolve API URL conflict"
## Alternative: Use a merge tool
git mergetool
## Abort the merge entirely (go back to before merge)
git merge --abort
Issue 4: Rebase vs Merge — When to Use Each
## MERGE: Creates a merge commit, preserves history
## Use for: merging feature branches into main
git checkout main
git merge feature-branch
## History: A---B---C---M (merge commit)
## /
## D---E
## REBASE: Replays commits on top of another branch, linear history
## Use for: updating a feature branch with latest main
git checkout feature-branch
git rebase main
## History: A---B---C---D'---E' (clean, linear)
## GOLDEN RULE: Never rebase commits that have been pushed and shared!
## Interactive rebase (see Issue 9 for squashing)
git rebase -i main
Issue 5: Cherry-Pick a Specific Commit
Scenario: You need one specific commit from another branch without merging the entire branch.
## Find the commit hash you want
git log --oneline other-branch
## Cherry-pick it onto your current branch
git cherry-pick abc1234
## Cherry-pick without committing (just apply the changes)
git cherry-pick --no-commit abc1234
## Cherry-pick a range of commits
git cherry-pick abc1234..def5678
## If there's a conflict during cherry-pick
git cherry-pick --continue ## after resolving
git cherry-pick --abort ## to cancel
Issue 6: Git Stash Operations
Scenario: You need to switch branches but have uncommitted changes you're not ready to commit.
## Stash your current changes (tracked files)
git stash
## Stash with a description (recommended!)
git stash push -m "WIP: user authentication form"
## Stash including untracked files
git stash push -u -m "WIP: including new files"
## Stash only specific files
git stash push -m "WIP: just the config" -- config.js utils.js
## List all stashes
git stash list
## stash@{0}: On feature: WIP: user authentication form
## stash@{1}: On main: WIP: API refactor
## Apply the most recent stash (keeps it in stash list)
git stash apply
## Apply a specific stash
git stash apply stash@{1}
## Pop the most recent stash (apply AND remove from list)
git stash pop
## Remove a stash without applying
git stash drop stash@{0}
## Clear ALL stashes (careful!)
git stash clear
## View stash contents before applying
git stash show -p stash@{0}
Issue 7: Recovering Deleted Branches with Reflog
What happened: You accidentally deleted a branch that had important commits not merged anywhere else.
## The reflog records every HEAD change for 90 days
git reflog
## Output:
## abc1234 HEAD@{0}: checkout: moving from deleted-branch to main
## def5678 HEAD@{1}: commit: important feature complete
## ghi9012 HEAD@{2}: commit: WIP: feature progress
## Find the last commit hash of the deleted branch
## Recreate the branch pointing to that commit
git branch recovered-branch def5678
## Verify the branch has your commits
git log --oneline recovered-branch
## Reflog also works for specific branches
git reflog show feature-branch
Issue 8: Recovering from a Bad Reset
What happened: You ran git reset --hard and lost important commits.
## Don't panic! Reflog has your back.
git reflog
## Find the commit hash BEFORE the reset
## abc1234 HEAD@{1}: commit: the work you lost
## Option 1: Reset back to it
git reset --hard abc1234
## Option 2: Create a new branch from it
git branch recovered abc1234
Issue 9: Squashing Commits with Interactive Rebase
Scenario: Your feature branch has 15 small commits like "fix typo", "WIP", "debugging". You want to combine them into clean, meaningful commits before merging.
## Squash the last 5 commits
git rebase -i HEAD~5
## This opens your editor with:
## pick abc1234 Add user model
## pick def5678 WIP: working on auth
## pick ghi9012 fix typo
## pick jkl3456 more auth work
## pick mno7890 Complete authentication
## Change to:
## pick abc1234 Add user model
## squash def5678 WIP: working on auth
## squash ghi9012 fix typo
## squash jkl3456 more auth work
## squash mno7890 Complete authentication
## Or use "fixup" instead of "squash" to discard commit messages
## pick abc1234 Add user model
## fixup def5678 WIP: working on auth
## fixup ghi9012 fix typo
## fixup jkl3456 more auth work
## fixup mno7890 Complete authentication
## Save and close. Git will combine all commits into one.
## You can then write a new commit message.
## Alternative shortcut: squash everything into 1 commit
git reset --soft HEAD~5
git commit -m "feat: add user authentication system"
Issue 10: Using Git Bisect to Find Bugs
Scenario: Something broke, but you don't know which commit caused it. Bisect uses binary search to find the guilty commit.
## Start bisect
git bisect start
## Mark the current (broken) commit as bad
git bisect bad
## Mark a known good commit (when things worked)
git bisect good v2.0.0
## or: git bisect good abc1234
## Git checks out a middle commit. Test it, then:
git bisect good ## if this commit works fine
git bisect bad ## if this commit is broken
## Repeat until Git identifies the first bad commit
## Output: "abc1234 is the first bad commit"
## Reset when done
git bisect reset
## AUTOMATED bisect with a test script
git bisect start HEAD v2.0.0
git bisect run npm test
## Git will automatically find the commit that broke the tests!
Issue 11: Git Submodule Issues
Common problems: Submodules not initialized, showing empty directories, or stuck on wrong commits.
## Clone a repo with submodules
git clone --recurse-submodules https://github.com/org/repo.git
## If you forgot --recurse-submodules:
git submodule init
git submodule update
## Or do both in one command:
git submodule update --init --recursive
## Update submodules to their latest commits
git submodule update --remote
## Fix "fatal: no submodule mapping found"
## This means .gitmodules is out of sync
git rm --cached path/to/submodule
## Then re-add it:
git submodule add https://github.com/org/submodule.git path/to/submodule
## Remove a submodule completely
git submodule deinit -f path/to/submodule
rm -rf .git/modules/path/to/submodule
git rm -f path/to/submodule
Issue 12: Large File Problems (Git LFS)
What happened: You accidentally committed a large file (binary, video, dataset) and now your repo is bloated.
## Install Git LFS
git lfs install
## Track large file types BEFORE committing them
git lfs track "*.psd"
git lfs track "*.zip"
git lfs track "*.mp4"
git lfs track "datasets/**"
## This creates/updates .gitattributes — commit it!
git add .gitattributes
git commit -m "Configure Git LFS tracking"
## If large files are already committed, migrate them to LFS:
git lfs migrate import --include="*.psd,*.zip" --everything
## Force push the rewritten history
git push --force-with-lease
## Check what LFS is tracking
git lfs ls-files
## Remove a large file from history entirely (if not using LFS)
## Using git-filter-repo (install: pip install git-filter-repo)
git filter-repo --path large-file.zip --invert-paths
Issue 13: .gitignore Not Working
What happened: You added a file pattern to .gitignore but Git keeps tracking the file.
Cause: .gitignore only prevents untracked files from being added. If a file was already tracked (committed) before adding it to .gitignore, Git continues tracking it.
## Fix: Remove from Git tracking (but keep the file on disk)
git rm --cached path/to/file
git rm --cached -r path/to/directory/
## For multiple files matching a pattern
git rm --cached *.log
git rm --cached -r node_modules/
## Commit the removal
git commit -m "Remove tracked files that should be ignored"
## Verify: the file should now show as untracked (and ignored)
git status
## Nuclear option: remove ALL tracked files that match .gitignore
git rm -r --cached .
git add .
git commit -m "Re-apply .gitignore rules"
## Debug: check why a file is/isn't ignored
git check-ignore -v path/to/file
Issue 14: Force Push Safely
Scenario: You need to force push after a rebase or amend, but you don't want to overwrite someone else's recent push.
## NEVER use --force on shared branches
## BAD: Blindly overwrites remote
git push --force ## DANGEROUS!
## GOOD: Only pushes if remote hasn't changed since your last fetch
git push --force-with-lease
## Even better: specify the expected remote ref
git push --force-with-lease=origin/feature-branch
## If --force-with-lease fails, it means someone pushed new commits
## Fetch and rebase on top of their work first:
git fetch origin
git rebase origin/feature-branch
git push --force-with-lease
Issue 15: Cleaning Untracked Files
## See what would be deleted (dry run — ALWAYS do this first!)
git clean -n
## Delete untracked files
git clean -f
## Delete untracked files AND directories
git clean -fd
## Delete untracked AND ignored files (full reset)
git clean -fdx
## Interactive mode (choose which files to delete)
git clean -i
Issue 16: Git Blame and Log Investigation
## See who last modified each line of a file
git blame src/app.js
## Blame with line range
git blame -L 50,70 src/app.js
## Ignore whitespace changes in blame
git blame -w src/app.js
## Search commit messages
git log --grep="fix login" --oneline
## Search code changes (find when a function was added/removed)
git log -S "functionName" --oneline
## View a file at a specific commit
git show abc1234:src/app.js
## Pretty log with graph
git log --oneline --graph --all --decorate -20
## Log changes to a specific file
git log --follow -p -- src/app.js
## Who contributed and how much
git shortlog -sn
Issue 17: Git Worktrees for Parallel Development
Scenario: You need to work on a hotfix while your feature branch is in the middle of a complex change. Instead of stashing, use worktrees.
## Create a new worktree for a different branch
git worktree add ../hotfix-directory hotfix-branch
## Create a worktree with a new branch
git worktree add -b emergency-fix ../emergency main
## Now you have two working directories:
## ./my-project (feature-branch)
## ../hotfix-directory (hotfix-branch)
## Both share the same .git repository
## List all worktrees
git worktree list
## Remove a worktree when done
git worktree remove ../hotfix-directory
## Prune stale worktree references
git worktree prune
Issue 18: Git Hooks (Pre-commit, Pre-push)
Git hooks run scripts automatically at certain points in the Git workflow. They live in .git/hooks/ directory.
Pre-commit Hook: Lint and Format Before Committing
#!/bin/sh
# .git/hooks/pre-commit (make executable: chmod +x)
echo "Running pre-commit checks..."
# Run linter
npm run lint
if [ $? -ne 0 ]; then
echo "Lint errors found. Fix them before committing."
exit 1
fi
# Check for console.log statements
if git diff --cached --name-only | xargs grep -l "console.log" 2>/dev/null; then
echo "WARNING: console.log found in staged files!"
echo "Remove debug statements before committing."
exit 1
fi
# Run type checking
npm run type-check
if [ $? -ne 0 ]; then
echo "Type errors found. Fix them before committing."
exit 1
fi
echo "All checks passed!"
Pre-push Hook: Run Tests Before Pushing
#!/bin/sh
# .git/hooks/pre-push
echo "Running tests before push..."
npm test
if [ $? -ne 0 ]; then
echo "Tests failed! Fix them before pushing."
exit 1
fi
echo "All tests passed. Pushing..."
Using Husky for Team-Wide Hooks
## Install Husky (manages hooks via package.json)
npm install --save-dev husky lint-staged
## Initialize Husky
npx husky init
## Add a pre-commit hook
echo "npx lint-staged" > .husky/pre-commit
## Add a pre-push hook
echo "npm test" > .husky/pre-push
// package.json
{
"lint-staged": {
"*.{js,ts,jsx,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{json,md,yml}": [
"prettier --write"
]
}
}
Issue 19: Signing Commits with GPG
Signed commits prove that you are the actual author, preventing impersonation.
## Generate a GPG key
gpg --full-generate-key
## Choose RSA and RSA, 4096 bits, key doesn't expire
## List your GPG keys
gpg --list-secret-keys --keyid-format=long
## Output:
## sec rsa4096/ABC1234DEF567890 2025-01-01 [SC]
## FINGERPRINT1234567890ABCDEF
## uid Your Name <your@email.com>
## Tell Git to use your GPG key
git config --global user.signingkey ABC1234DEF567890
## Sign all commits by default
git config --global commit.gpgsign true
## Sign a single commit manually
git commit -S -m "This commit is signed"
## Verify a signed commit
git log --show-signature -1
## Export your public key (add to GitHub/GitLab)
gpg --armor --export ABC1234DEF567890
## On GitHub: Settings > SSH and GPG Keys > New GPG Key
## Paste the exported public key
Issue 20: SSH Key Setup for GitHub/GitLab
SSH keys let you push and pull without entering your username and password every time.
## Check for existing SSH keys
ls -la ~/.ssh/
## Generate a new SSH key
ssh-keygen -t ed25519 -C "your@email.com"
## Press Enter for default file location (~/.ssh/id_ed25519)
## Enter a passphrase (recommended!)
## Start the SSH agent
eval "$(ssh-agent -s)"
## Add your key to the agent
ssh-add ~/.ssh/id_ed25519
## Copy the public key
## Linux:
cat ~/.ssh/id_ed25519.pub | xclip -selection clipboard
## macOS:
pbcopy < ~/.ssh/id_ed25519.pub
## Windows (PowerShell):
Get-Content ~/.ssh/id_ed25519.pub | Set-Clipboard
## Add to GitHub:
## GitHub.com > Settings > SSH and GPG Keys > New SSH Key
## Paste the copied key
## Test the connection
ssh -T git@github.com
## Output: "Hi username! You've successfully authenticated..."
## Switch a repo from HTTPS to SSH
git remote set-url origin git@github.com:username/repo.git
## Verify
git remote -v
Multiple GitHub/GitLab Accounts (SSH Config)
# ~/.ssh/config
# Personal GitHub
Host github.com-personal
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_personal
# Work GitHub
Host github.com-work
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_work
# GitLab
Host gitlab.com
HostName gitlab.com
User git
IdentityFile ~/.ssh/id_ed25519_gitlab
## Clone using the configured host alias
git clone git@github.com-personal:myuser/repo.git ## uses personal key
git clone git@github.com-work:company/repo.git ## uses work key
Credential Storage Options
## Cache credentials in memory for 15 minutes
git config --global credential.helper cache
## Cache for 8 hours
git config --global credential.helper "cache --timeout=28800"
## Store credentials permanently (plain text — less secure)
git config --global credential.helper store
## Use system keychain (RECOMMENDED)
## macOS:
git config --global credential.helper osxkeychain
## Windows:
git config --global credential.helper manager
## Linux (GNOME):
git config --global credential.helper libsecret
Quick Reference: Git Troubleshooting Cheat Sheet
| Problem | Quick Fix Command |
|---|---|
| Undo last commit (keep changes) | git reset --soft HEAD~1 |
| Undo last commit (discard changes) | git reset --hard HEAD~1 |
| Fix detached HEAD | git checkout -b new-branch |
| Abort a bad merge | git merge --abort |
| Stash changes quickly | git stash push -m "description" |
| Recover deleted branch | git reflog → git branch name hash |
| Squash last N commits | git rebase -i HEAD~N |
| Find bug-introducing commit | git bisect start |
| Cherry-pick a commit | git cherry-pick hash |
| .gitignore not working | git rm --cached file |
| Safe force push | git push --force-with-lease |
| Clean untracked files | git clean -fd (preview: -n) |
| Who wrote this line? | git blame file |
| Work on two branches simultaneously | git worktree add path branch |
| Sign commits | git commit -S -m "message" |
| Test SSH connection | ssh -T git@github.com |
| Switch HTTPS to SSH | git remote set-url origin git@... |
| View pretty log | git log --oneline --graph --all -20 |
| Search commit history | git log -S "search term" |
| Initialize submodules | git submodule update --init --recursive |