Stage 2 · Tools
Branching & Merging
Rebasing
Rewrite history to keep a clean, linear commit graph — interactive rebase, squash, fixup, and rebase vs merge.
What Rebase Does
Rebasing takes a series of commits from one branch and replays them onto a different base commit. The result looks the same as if you had started your work from the target branch, even though you actually started from a different point.
Unlike merging, which creates a merge commit to connect two histories, rebasing rewrites the commit history itself. Each commit gets a new SHA hash because its parent has changed.
Rebasing creates new commits with new SHA hashes. The original commits still exist in the reflog until they are garbage-collected. This is why you must never rebase commits that have been pushed to a shared branch.
Basic Rebase
# Start on your feature branch
git switch feature/api
# Rebase onto main — replays feature commits on top of main
git rebase main
# What happened:
# Before:
# * g7h8i9j (main) Fix error handling
# | * d4e5f6g (feature/api) Add rate limiting
# | * a1b2c3d Add API module
# |/
# * f4e5d6c Initial commit
#
# After:
# * d4e5f6g' (feature/api) Add rate limiting
# * a1b2c3d' Add API module
# * g7h8i9j (main) Fix error handling
# * f4e5d6c Initial commitRebase took the two commits from feature/api and replayed them on top of main. The commits have new hashes (marked with ') because their parent changed. History is now linear.
# After rebasing, you need force-push because history changed
git push --force-with-lease origin feature/api
# --force-with-lease is safer than --force
# It fails if someone else pushed to the branch since you last fetchedBecause rebase creates new commits, your local branch diverges from the remote. --force-with-lease is the safe option — it checks that the remote hasn't changed since you last pulled.
Interactive Rebase
Interactive rebase (git rebase -i) opens an editor listing every commit in the range. You can reorder, squash, reword, edit, or drop each commit before Git replays them. This is the most powerful tool for cleaning up history before sharing.
# Rebase interactively on the last 3 commits
git rebase -i HEAD~3
# Or rebase onto main from where you branched
git rebase -i mainGit opens your editor with a todo list of commits. Each line shows a command (pick by default) and the commit message. Change the commands to reshape your history.
pick a1b2c3d Add API module
pick d4e5f6g Add rate limiting
pick g7h8i9j Fix typo in API module
# Rebase f4e5d6c..g7h8i9j onto f4e5d6c
#
# Commands:
# p, pick = use commit as is
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command using shell
# d, drop = remove commit entirelySave and close the editor. Git will execute the commands in order. For squash and fixup, it will prompt you to edit the combined commit message.
Squash, Fixup, and Reword
| Command | Effect | Commit Message | Use Case |
|---|---|---|---|
| pick | Keep commit as is | Original message | Default — commit is good |
| squash | Merge into previous commit | Opens editor to combine messages | Combining related WIP commits |
| fixup | Merge into previous commit | Discard this commit's message | Fixing a typo or small change |
| reword | Keep commit, change message | Opens editor for new message | Fixing a commit message |
pick a1b2c3d Add API module
fixup d4e5f6g Add rate limiting
squash g7h8i9j Fix typo in API moduleAfter this rebase: commit a1b2c3d absorbs the other two. The rate limiting change is merged silently (fixup), and the typo fix is merged with its message combined (squash). You get one clean commit.
# Shortcut: squash the last 3 commits into one
git reset --soft HEAD~3
git commit -m "Add complete API with rate limiting"git reset --soft HEAD~3 moves HEAD back 3 commits but keeps all changes staged. The new commit includes everything from the squashed commits. This is faster than interactive rebase when you just want to squash.
Reordering Commits
In interactive rebase, you can reorder commits by simply moving lines up or down in the editor. Git replays them in the new order.
# Before (original order)
pick a1b2c3d Add API module
pick d4e5f6g Add rate limiting
pick g7h8i9j Fix typo in API module
# After reordering (move fix before rate limiting)
pick a1b2c3d Add API module
pick g7h8i9j Fix typo in API module
pick d4e5f6g Add rate limitingGit will replay the commits in the new order. This can change the behavior if later commits depend on earlier ones, so be careful with reordering.
Rebase --onto
The --onto flag lets you transplant a branch onto a different base. This is useful for moving commits between branches or extracting a subset of commits.
# Move commits from feature/api that are NOT in feature/auth
# onto main
git rebase --onto main feature/auth feature/api
# Scenario: You have commits A-B-C on feature/api
# but feature/auth shares commits A-B
# This moves only commit C onto main--onto <newbase> <upstream> <branch> replays commits from <upstream> to <branch> onto <newbase>. This is one of Git's most powerful (and least understood) operations.
If you fixed a bug on main but need the same fix on an older release branch, git rebase --onto release/2.0 main~1 main transplants just that fix commit onto the release branch.
Rebase vs Merge
| Aspect | Rebase | Merge |
|---|---|---|
| History style | Linear — no merge commits | Non-linear — merge commits connect branches |
| Commit hashes | Rewritten (new SHA for each commit) | Original commits preserved |
| Traceability | Harder — history looks like one line | Easier — merge commits show integration points |
| Shared branches | Dangerous — rewrites shared history | Safe — additive only |
| Conflict resolution | Replay conflicts one commit at a time | Resolve all conflicts at once |
| Best for | Cleaning up feature branches | Integrating completed features |
Many teams use both: rebase locally to clean up a feature branch, then merge (or squash merge) it into main. This gives you clean commits on the branch and a clear integration point on main.
The Golden Rule
If commits have been pushed and other people might have them, do not rebase. Rebasing rewrites commit hashes, which means anyone who based work on the old commits will have a diverged history. Rebase only your own local, unpushed commits.
# Safe: rebase your local feature branch before pushing
git switch feature/api
git rebase main
git push --force-with-lease
# Dangerous: rebasing main after others have pulled
git switch main
git rebase feature/api # NEVER DO THIS on shared branchesFollow this rule: rebase your own feature branches freely, but never rebase a branch that others have based work on. When in doubt, merge.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.