Use Git Effectively from the Command Line
Master Git from the terminal: branches, merge vs rebase, interactive rebase to clean history, stash for context switching, and reflog to recover lost commits.
Before you start
- ▸Git 2.23 or later installed (check with: git --version)
- ▸A local Git repository initialized or cloned
- ▸Basic familiarity with git add, git commit, and git push
Git's command-line interface gives you direct, precise control that no GUI can fully replicate. This guide covers the everyday workflows that separate a user who only knows git commit from one who can untangle history, recover lost work, and keep a clean project timeline. Each section builds on real scenarios you'll hit within weeks of working on any non-trivial project.
Branches: Creating, Switching, and Cleaning Up
Branches are cheap pointers to commits. Make one for every feature or fix—always. Never work directly on main or master.
Create and switch in one step
git switch -c feature/user-auth
git switch is the modern replacement for git checkout for branch operations (Git 2.23+). Use git checkout only if you're on a system running Git older than 2.23.
List branches
# Local branches
git branch
# Remote-tracking branches too
git branch -a
Delete merged branches
# Safe delete — fails if branch is not fully merged
git branch -d feature/user-auth
# Force delete (use when you've merged via a PR and hashes differ)
git branch -D feature/old-experiment
Prune stale remote-tracking refs
git fetch --prune
Run this regularly. Remote branches deleted by teammates will otherwise clutter git branch -a forever.
Merge vs. Rebase: Choosing the Right Tool
Both integrate changes from one branch into another. The difference is what the history looks like afterwards.
- Merge creates a new merge commit with two parents. History is truthful—you can see exactly when branches diverged and reunited.
- Rebase replays your commits on top of another branch's tip. History looks linear, as if you always branched from the latest point.
Standard merge
git switch main
git merge feature/user-auth
Rebase your branch onto main before merging
git switch feature/user-auth
git rebase main
After rebasing, a merge into main becomes a fast-forward—no extra merge commit, clean linear history.
The golden rule: never rebase commits that have already been pushed to a shared remote branch. Rebase rewrites commit hashes; anyone else working from those commits will have a broken history.
Interactive Rebase: Rewriting Local History
Interactive rebase lets you edit, squash, reorder, or drop commits before they're shared. Use it to clean up a messy local branch before opening a pull request.
Open the interactive editor
# Rewrite the last 4 commits
git rebase -i HEAD~4
Your $EDITOR opens with a list of commits and commands. The most useful actions:
- pick — keep the commit as-is (default)
- reword — keep the commit but edit the message
- squash — fold into the previous commit, combining messages
- fixup — like squash but silently discards this commit's message
- drop — delete the commit entirely
Typical cleanup: squash WIP commits
Say your last four commits are: feat: add login form, wip, fix typo, wip 2. Change the file so the three noisy commits say fixup:
pick a1b2c3 feat: add login form
fixup d4e5f6 wip
fixup 7890ab fix typo
fixup cdef12 wip 2
Save and close the editor. Git replays the commits, producing a single tidy commit.
Abort if things go wrong
git rebase --abort
Stash: Shelving Unfinished Work
You're mid-feature when you need to switch context urgently. git stash saves your dirty working tree and index, leaving a clean checkout.
Save and restore
# Stash everything tracked (modified + staged)
git stash push -m "half-done login validation"
# Switch context, do your urgent work, then come back
git switch main
# ... fix the bug ...
git switch feature/user-auth
# Restore the most recent stash
git stash pop
Include untracked files
git stash push -u -m "includes new files"
Inspect and manage multiple stashes
# List all stashes
git stash list
# Show what's in stash entry 1
git stash show -p stash@{1}
# Apply a specific stash without removing it from the list
git stash apply stash@{1}
# Delete a specific stash
git stash drop stash@{1}
# Nuke all stashes
git stash clear
git stash pop is equivalent to apply + drop on the most recent entry. Prefer apply when you're unsure—you can always drop manually after confirming the restore worked.
Reflog: Your Safety Net
The reflog records every position HEAD has ever pointed to on your local machine. It's how you recover commits that appear to be gone—after a bad reset, an accidental branch delete, or a rebase that went sideways.
View the reflog
git reflog
Output looks like this (yours will differ):
e3f1a2b HEAD@{0}: rebase finished: returning to refs/heads/feature/user-auth
a1b2c3d HEAD@{1}: rebase: feat: add login form
7890abc HEAD@{2}: checkout: moving from main to feature/user-auth
...
Recover a dropped commit
# Identify the hash of the commit you want back, then:
git checkout -b recovery/lost-work 7890abc
This creates a new branch at that commit so you can inspect or cherry-pick from it.
Undo a hard reset
# You ran: git reset --hard HEAD~3 by mistake
# Find the pre-reset position in the reflog
git reflog
# Reset back to it
git reset --hard HEAD@{2}
Reflog entries expire after 90 days by default (gc.reflogExpire). If you need to recover old work, act quickly and don't run git gc in the meantime.
Verification: Inspect the Graph
After rebasing, squashing, or merging, inspect the actual commit graph to confirm what you intended:
git log --oneline --graph --decorate --all
A clean rebase produces a straight vertical line of commits. A merge produces a visible fork-and-join. If something looks wrong, the reflog is your undo button.
Troubleshooting
Rebase conflict loop
During a rebase, Git pauses at each conflicting commit. Resolve the conflict in your editor, then:
git add path/to/resolved/file
git rebase --continue
If a commit becomes empty after resolution (because the change already exists upstream), skip it:
git rebase --skip
Stash apply conflict
If git stash pop produces conflicts, the stash entry is not removed automatically. Resolve conflicts, git add the files, and then drop the stash manually:
git stash drop stash@{0}
Pushed to the wrong branch
If you accidentally pushed commits to a shared branch and need to remove them, communicate with your team first. A forced push rewrites shared history and will break everyone else's clones:
# Only safe on branches no one else uses
git push --force-with-lease origin feature/my-branch
--force-with-lease is safer than --force: it refuses to overwrite if someone else has pushed since your last fetch.
Frequently asked questions
- When should I use merge instead of rebase?
- Use merge on long-lived shared branches where an accurate record of integration points matters. Use rebase to tidy up short-lived personal feature branches before they're reviewed or merged.
- Is it safe to interactive-rebase commits I've already pushed?
- Only if no one else has based work on those commits. Rebase rewrites commit hashes; force-pushing will break teammates' local histories. On solo feature branches where you're the only pusher, it's generally fine with --force-with-lease.
- What's the difference between 'git stash pop' and 'git stash apply'?
- 'pop' applies the most recent stash and removes it from the stash list in one step. 'apply' leaves the stash entry intact, which is safer when you're not sure the apply will succeed cleanly.
- How long does the reflog keep entries?
- By default, reachable reflog entries expire after 90 days and unreachable ones after 30 days. The clock resets each time you access an entry. Don't rely on it as a long-term backup.
- What does 'git fetch --prune' actually delete?
- It deletes your local remote-tracking refs (e.g., origin/old-feature) for branches that no longer exist on the remote. It does not delete your local branches—those must be removed manually with 'git branch -d'.
Related guides
Bash Arrays and Associative Arrays
Master bash indexed and associative arrays: declaration, element access, looping, mapfile, namerefs, and practical patterns for real scripting work.
Bash Functions and Variable Scoping
Master Bash function scoping with local variables, source-based libraries, correct use of return codes, and array passing techniques including namerefs.
Bash Loops: for, while and until
Learn all three Bash loop types — for, while, and until — with practical, copy-paste examples covering file iteration, counting, polling, and safe line reading.
Bash Scripting for Beginners
Learn Bash scripting from scratch: shebang lines, variables, conditionals, loops, and arguments, plus a real backup script to tie it all together.