We all learn Git the hard way. You start with add, commit, push — and that works until the day you accidentally commit to main or delete a branch you meant to keep.
Here are 10 Git commands I discovered way later than I should have. Each one saved me from a real mess.
1. git switch — The Safe Checkout
I used git checkout for everything: switching branches, restoring files, creating new branches. Until one day I ran:
git checkout some-file.txt
expecting to switch to a branch, and instead wiped my working directory changes.
git switch separates branch switching from file restoration:
git switch feature-branch # switch branches
git switch -c new-feature # create and switch
git restore handles the file side:
git restore some-file.txt # discard uncommitted changes
Two commands, one job each. No more accidental wipes.
2. git log --oneline --graph — See the Story
Plain git log dumps a wall of text. Add --oneline --graph and you see the actual branch topology:
git log --oneline --graph --all --decorate
* 7a3f92e (HEAD -> main) fix: handle null pointer
| * a8d2c11 (feature-x) feat: add retry logic
|/
* b4e3391 chore: bump deps
I use --all because branches you're not on matter too. Onboard this as an alias:
git config --global alias.tree "log --oneline --graph --all --decorate"
git tree
3. git commit --amend — Edit Without Clutter
Pushed a typo in the last commit message? Forgot to stage that one file?
git add forgotten-file.py
git commit --amend --no-edit # add file without changing message
git commit --amend -m "Better message" # fix the message
⚠️ Only amend commits you haven't pushed yet. Once pushed, amend rewrites history and everyone who pulled will hate you.
4. git stash push -m "description" — Named Stashes
Without a message, git stash creates nameless entries. Three stashes later you have no idea which is which.
git stash push -m "WIP: login refactor"
git stash push -m "debug logging experiment"
git stash list
# stash@{0}: On main: debug logging experiment
# stash@{1}: On main: WIP: login refactor
git stash pop stash@{1}
I now write a description every single time — saves me 5 minutes of "which stash was that" every week.
5. git rebase -i — Clean History Before You Share
Your branch has 7 commits that say "wip", "fix", "fix again". Before merging, squash them into one meaningful commit:
git rebase -i HEAD~7
In the editor, change pick to squash (or just s) for the commits you want to merge upward. Keep the first commit's message.
I rebase before every merge. The result is a linear, readable history that your future self and your teammates will thank you for.
6. git bisect — Find the Bug Automatically
A feature that worked last week is broken today. You have 50 commits between "worked" and "broken".
git bisect start
git bisect bad # current commit is broken
git bisect good abc123 # this commit worked
# Git checks out a commit in the middle.
# You test it:
git bisect good # if it works
git bisect bad # if it's broken
# Repeat 4-5 times. Git finds the exact commit.
git bisect reset
bisect runs a binary search. 50 commits → ~6 checks. 500 commits → ~9 checks. I use this monthly and it always finds the culprit.
Automate it if you have a test command:
git bisect run npm test
7. git worktree — Work on Two Branches at Once
You're deep in feature-x and someone says "urgent hotfix on main".
Instead of stashing or force-closing your editor:
git worktree add ../hotfix-fix main
cd ../hotfix-fix
# fix the bug, commit, push
cd ..
git worktree remove ../hotfix-fix
Each worktree is a full checkout in its own directory. Open it in another editor tab, work on it, switch back. No stash, no context loss.
I keep 2-3 worktrees active during a typical dev day.
8. git diff --cached — Review Before You Commit
git diff shows unstaged changes. But I want to review what I'm about to commit:
git diff --cached
This shows only the staged diff. It's replaced by the git commit -v experience:
git commit -v # opens editor with diff in the message area
See exactly what goes in before it goes in. Catches half-baked code every time.
9. git clean -nd — Preview Destructive Deletes
Untracked files accumulate: build artifacts, .env.bak, node_modules you somehow regenerated.
git clean -nd
# Would remove .env.bak
# Would remove dist/
The -n flag is a dry run. -d targets directories. When you're sure:
git clean -fd
I never run git clean without -n first. Too many stories of someone nuking a folder they needed two days ago.
10. git blame --ignore-revs-file — Skip Formatting Commits
git blame is the first thing I reach for when I see weird code. But auto-formatters and mass renames pollute the blame, pointing at the person who ran prettier instead of the person who wrote the bug.
# Save a file listing commits that are "noise"
echo "abc123def" > .git-blame-ignore-revs
git blame --ignore-revs-file .git-blame-ignore-revs main.py
Commit your .git-blame-ignore-revs file to the repo so everyone benefits. GitHub even respects it natively when viewing blame on pull requests.
These 10 commands won't make you a Git guru overnight. But they'll save you from the specific, painful moments we all hit: accidental deletions, messy histories, broken branches, and wasted hours tracking down bugs.
Start with one: alias git tree and run it on your current project. See your branches differently.
Have a Git command that made you say "I wish I learned this sooner"? Drop it in the comments.
Top comments (0)