Before we dive in: I share practical insights like this weekly. Join developers and founders getting my newsletter with real solutions to engineering and business challenges.
I've been using Git for far too many years and I still mess up regularly. Just last week I was running three different development sessions - one adding API routes, another updating documentation, and a third refactoring authentication logic. Everything was going smoothly until I accidentally merged the wrong branch and suddenly had database migration files mixed in with my API changes. The kind of mess that makes you question your life choices.
Here's every Git fix I wish I'd known from day one. Not theory, not textbook examples - real solutions to problems that actually happen when you're building things.
The Foundation: Understanding Git's Undo Philosophy
Git has what feels like seventeen different ways to undo things, which is both its strength and its biggest source of confusion. The key insight that changed everything for me: Git isn't just storing your files, it's managing three different "trees" of information.
The Three Trees:
- Working Directory: The actual files you're editing
- Index (Staging Area): Files ready to be committed
- HEAD: Your last commit
When you're managing multiple development workflows simultaneously, understanding these concepts becomes even more critical. Each isolated session has its own working directory, but they can share the same repository history.
I learned this the hard way when I had automated commits happening in my main directory while I was testing different approaches in parallel sessions. The result? A completely tangled history that took me two hours to untangle.
The trick is knowing which "undo" command affects which tree:
git checkout
affects your working directorygit reset
can affect the index and HEADgit revert
creates new commits
Once you understand this, the seemingly random collection of Git commands starts making sense.
Undoing Recent Work: Your Daily Lifesaving Operations
"I just committed something wrong - now what?"
This happens to me at least twice a week. Sometimes it's a commit message that makes no sense ("fix stuff"), sometimes I've included files that shouldn't be there, and occasionally I commit something that completely breaks the build.
Scenario 1: Wrong Commit Message
# You just did this
git commit -m "fix stuff"
# Fix it
git commit --amend -m "Add input validation to user registration endpoint"
GitPilotAI usually prevents this problem by generating decent commit messages, but when you're working quickly or testing something in a ccswitch session, you might bypass it and end up with rubbish.
Scenario 2: Added Wrong Files
This happened to me just last week when I was working on API routes and accidentally included my .env
file with database credentials.
# Remove the file from the commit but keep it in working directory
git reset --soft HEAD~1
git reset HEAD .env
git commit -m "Add new API endpoints for user management"
The --soft
flag keeps your changes in the staging area, so you can selectively unstage the problematic files.
Scenario 3: Completely Wrong Commit
Sometimes you need to nuke the entire commit and start over:
# Nuclear option - removes commit and changes
git reset --hard HEAD~1
# Safer option - removes commit but keeps changes
git reset HEAD~1
I prefer the safer option because I'm paranoid about losing work. Even if the commit was rubbish, there might be some useful changes worth salvaging.
Fixing Merge Disasters: When Things Go Sideways
Merge conflicts are bad, but merge disasters are worse. These happen when you merge the wrong branch, merge at the wrong time, or create a merge commit that breaks everything.
I ran into this recently when working with multiple feature branches. I had three going - one for API changes, one for documentation updates, and one for authentication refactoring. I intended to merge them in a specific order, but got distracted and merged them all at once. The result was a complete mess.
Reverting a Merge Commit
The trickiest part about reverting merges is that Git doesn't know which parent you want to revert to:
# Find the merge commit
git log --oneline --graph
# Revert to the first parent (usually main/master)
git revert -m 1 <merge-commit-hash>
# Or revert to the second parent (the feature branch)
git revert -m 2 <merge-commit-hash>
The "Revert a Revert" Problem
This is where things get really confusing. Say you revert a merge, then later realise you actually needed those changes:
# You reverted a merge
git revert -m 1 abc123
# Now you want those changes back
# You can't just merge the original branch again
# You need to revert the revert
git revert def456
My Workflow for Merge Management
When I'm working with multiple branches, I've learned to be very deliberate about merging:
- Complete one feature and merge it first
- Update all other branches with the latest main
- Resolve any conflicts in the isolated branches
- Merge the next feature
This prevents the "everything merged at once" disasters that used to plague my workflow.
# In each feature branch before merging
git fetch origin
git rebase origin/main
# Fix any conflicts here, in isolation
# Then merge with confidence
File and Staging Gymnastics
The staging area is your safety net, but sometimes you need fine-grained control when things go wrong.
Unstaging Files
# Unstage specific files
git restore --staged filename.go
# Unstage everything
git restore --staged .
# The old way (still works)
git reset HEAD filename.go
Removing Files from the Last Commit
This happens when you include more than you wanted, or when you're working quickly and accidentally commit temp files:
# Remove file from commit but keep in working directory
git reset --soft HEAD~1
git restore --staged unwanted-file.txt
git commit -c ORIG_HEAD
Selective Stashing
When you're juggling multiple branches, you often need to stash specific files:
# Stash only specific files
git stash push -m "temp changes" src/api.go src/handlers.go
# Stash everything except specific files
git stash push --keep-index -m "keep staged files"
Real Example
Just yesterday, I was working on changes that modified more files than expected. I wanted to commit the API changes but not the configuration tweaks:
# Stage only what I want
git add src/api/
git add src/handlers/
# Stash the rest
git stash push -m "config changes for later"
# Commit the API changes
git commit -m "Add new user management endpoints"
# Later, when focusing on config
git stash pop
Advanced Recovery Operations
Sometimes your local branch gets completely out of sync with remote, or you need files from different places. These are the nuclear options.
Force Pull (When Remote is Truth)
# Nuclear reset to match remote exactly
git fetch origin
git reset --hard origin/main
I use this when a development session has gone completely off the rails and I want to start fresh with the latest remote state.
Getting Files from Other Branches
When you're working on a feature and need a file from your main project or another branch:
# Grab specific file from another branch
git checkout main -- src/config.go
# Grab file from specific commit
git checkout abc123 -- src/utils.go
Resetting Branch to Match Remote
# Reset local branch to exact remote state
git reset --hard origin/main
# Alternative: delete and recreate
git checkout main
git branch -D problematic-branch
git checkout -b problematic-branch origin/problematic-branch
My ccswitch Recovery Workflow
Sometimes a ccswitch session becomes too messy to salvage:
# List current sessions
ccswitch list
# Create a backup of useful changes
git stash push -m "potential useful bits"
# Nuke the session and start fresh
ccswitch cleanup messy-session-name
ccswitch
# Start new session with same description
git stash list
# Selectively apply any useful bits from backup
Command Shortcuts and Power User Techniques
These shortcuts can save time or destroy your day, depending on how you use them.
Commit Shortcuts
# Stage and commit in one go
git commit -am "Quick fix for validation bug"
# Commit all tracked files (ignores new files)
git commit -a
I rarely use -a
anymore since I generally prefer explicit staging, but it's useful for quick fixes when you're working fast.
Interactive Rebase
This is essential for cleaning up feature branches before merging:
# Clean up last 3 commits
git rebase -i HEAD~3
In the editor:
pick abc123 Add user validation
squash def456 Fix typo in validation
squash ghi789 Add missing tests
Power User Git Add
# Stage parts of files interactively
git add -p
# Stage all changes including deletions
git add -A
The -p
flag is brilliant when your changes are broader than what you want to commit at once.
Putting It All Together: The Complete Workflow
After years of Git disasters and building tools to solve workflow problems, I've developed an approach that combines automation with solid Git fundamentals.
The Approach
For routine development:
- Use automation tools where they make sense
- Let tooling handle repetitive Git mechanics
- Focus on coding, not commit message crafting
For complex work:
- Isolate different tasks in separate branches
- Apply manual Git techniques when things go wrong
- Clean up branches with interactive rebase before merging
For recovery:
- Know your undo operations cold
- Always prefer safer options that preserve work
- Use the nuclear options only when necessary
Your Git Safety Checklist
Before any dangerous Git operation:
- Create a backup branch:
git branch backup-$(date +%s)
- Know your escape route: Can you get back to where you started?
- Check what you're about to lose:
git log --oneline -10
- Test in a ccswitch session first: If you're unsure, try it in isolation
Tool Integration in Practice
Here's how automation and manual Git knowledge work together in my daily workflow:
Morning routine:
# Update main project
git checkout main && git pull
# Start focused work on a feature
git checkout -b feature/auth-refactor
# Work in isolation, commit frequently
When things go wrong:
# In feature branch - fix problems quickly
git reset --hard HEAD~5 # No risk to main project
git checkout main -- src/auth.go # Grab fresh copy
# In main project - be more careful
git stash # Preserve work
git reset --soft HEAD~1 # Safer undo
End of day cleanup:
# Clean up feature branch
git rebase -i HEAD~8 # Squash commits
git checkout main
git merge feature/auth-refactor # Clean merge
The combination of AI-assisted development and proper tooling to manage it feels like a glimpse into how software development workflows will continue to evolve. When you have GitPilotAI automating routine commits and ccswitch managing parallel AI sessions, the entire development process becomes more fluid and efficient.
Having these tools doesn't eliminate the need to understand Git deeply. If anything, it makes this knowledge more important. When you're running multiple AI agents working on different aspects of your codebase simultaneously, each in their own isolated environment, then bringing those changes together through normal PR processes, you need to know how to fix things when they inevitably go sideways.
Git will always find new ways to surprise you. The difference is that now, instead of losing half a day to Git disasters, you can fix problems in minutes and get back to building.