Git Concepts — Visual Guide
Merge
Initial State
Two branches co-exist: main (blue) has commits A→B→C; feature (green) diverged from B and added D→E.
Step 1 of 3
Key Points
- Merge preserves the complete history of both branches — nothing is rewritten
- A merge commit has two parents, recording exactly where the branches joined
- Fast-forward merge: if no divergence, Git just moves the branch pointer — no merge commit needed
- Use merge when you want to keep a clear record of feature branch lifetimes
- Conflicts are resolved once, at merge time, then committed
Commands
# Switch to target branch and merge
git switch main
git merge feature
# Force a merge commit (no fast-forward)
git merge —no-ff feature
# Abort a conflicted merge
git merge —abort
Rebase
Initial State
feature branch (E, F) diverged from B. main has since moved to D. We want to replay feature commits on top of D.
Step 1 of 4
Key Points
- Rebase replays your commits onto a new base — commits get new hashes (E→E’, F→F’)
- History becomes linear — easier to read with
git log - Golden rule: never rebase shared/public branches — rewriting history breaks everyone else’s clone
- Conflicts are resolved per-commit, one at a time, as each is replayed
- Old commits (E, F) become unreachable and are eventually garbage-collected
Commands
# Rebase feature onto latest main
git switch feature
git rebase main
# Continue after resolving conflicts
git rebase —continue
# Abort and return to original state
git rebase —abort
# Then fast-forward merge (clean history)
git switch main
git merge —ff-only feature
Squash
Before Squash
5 WIP commits clutter the branch history with trivial messages that add noise to the project log.
Step 1 of 3
Key Points
- Squash combines multiple commits into one — useful before merging a PR to keep history clean
git rebase -i HEAD~Nopens an editor where you mark commits as squash or fixupgit merge --squash featuremerges all feature changes as one unstaged diff, then you commit- Like rebase, squash rewrites history — only do it on your local/feature branches before merge
- Squash = cleaner
git log, but you lose granular commit-level rollback ability
Commands
# Interactive rebase — squash last 5 commits
git rebase -i HEAD~5
# In editor: change ‘pick’ → ‘squash’ or ‘s’
# Squash entire branch into one commit on main
git switch main
git merge —squash feature
git commit -m “feat: complete feature”
Cherry-Pick
Identify the Commit
Commit D on main contains a critical bug fix. We need it on the feature branch without merging all of main.
Step 1 of 3
Key Points
- Cherry-pick copies the changes from a specific commit and applies them to the current branch
- The new commit D’ gets a new hash but contains identical changes to D
- The original commit D stays unchanged on main — it is not moved or deleted
- Common use: apply a hotfix to both main and a release branch simultaneously
- Duplicate commits can cause confusion on future merges — use sparingly
Commands
# Find the commit hash you want
git log —oneline main
a1b2c3d D: fix critical null pointer
# Cherry-pick onto current branch
git switch feature
git cherry-pick a1b2c3d
# Pick multiple commits
git cherry-pick a1b2c3d..f6e5d4c
# Abort if conflicts arise
git cherry-pick —abort
Fetch
Remote Is Ahead
Remote has 2 new commits (R4, R5) that don’t exist locally yet. Your local main is behind.
Step 1 of 3
Key Points
- fetch downloads remote commits into
origin/mainbut does NOT move your localmain - It is always safe — fetch never modifies your working tree or local branches
- pull = fetch + merge (or fetch + rebase with
--rebase) - After fetching, use
git log origin/mainto inspect what arrived before integrating - Best practice: fetch first, review, then merge/rebase deliberately
Commands
# Download without touching local branches
git fetch origin
# Inspect what arrived
git log origin/main —oneline
# Integrate via merge
git merge origin/main
# Or: fetch + rebase in one step
git pull —rebase
# See divergence at a glance
git status
Your branch is behind ‘origin/main’ by 2 commits
Reset
Before Reset
HEAD is at commit C. We run git reset A in three different modes — each one affects a different number of layers.
Step 1 of 2
Key Points
- —soft: moves HEAD only. Your staged changes and working files are untouched — useful to re-commit with a better message
- —mixed (default): moves HEAD and clears the index. Changes return to unstaged in your working directory
- —hard: moves HEAD, clears index AND discards all working directory changes — this is destructive and irreversible
- All three modes rewrite local history — never reset commits already pushed to a shared branch
- Recovery:
git reflogcan find lost commits after soft/mixed. Hard resets may be unrecoverable
Commands
# Move HEAD back 2 commits, keep staged
git reset —soft HEAD~2
# Move HEAD back, unstage changes (default)
git reset HEAD22
git reset —mixed HEAD
# ⚠ Discard EVERYTHING — cannot undo
git reset —hard HEAD~2
# Find lost commits after reset
git reflog
Stash
Dirty Working Directory
You have uncommitted changes in 3 files but need to switch branches urgently. You can’t commit yet — stash saves the day.
Step 1 of 4
Key Points
- Stash saves your dirty working directory and index to a stack, then reverts to a clean HEAD state
- The stash is a stack — multiple stashes can be saved, stash@{0} is always the most recent
git stash popapplies the top stash AND removes it;git stash applykeeps the stash entry- Stash works across branches — stash on feature, switch to main, pop on main (with care)
- Untracked files are NOT stashed by default — use
-uflag to include them
Commands
# Save dirty state, clean working dir
git stash
git stash push -m “WIP: login form”
# Include untracked files
git stash -u
# List all stashes
git stash list
stash@{0}: WIP on feature: login form
# Apply and remove top stash
git stash pop
# Apply without removing
git stash apply stash@{0}
Enjoyed this post?
Get the next one in your inbox — only when I ship something worth reading.
Newsletter form not configured.
Or follow on Substack for the newsletter.
Comments via GitHub Discussions
Comments not configured. Set GISCUS env vars to enable.