What you will achieve
You will take a feature branch that has fallen behind main, rebase it cleanly onto the new tip, resolve any conflicts that arise, and push the rebased branch back to your remote. By the end, you will be comfortable with the most common rebase scenario.
Why rebase, not merge?
Merging main into your feature branch produces extra merge commits and tangles the history. Rebasing replays your commits on top of the latest main, producing a clean linear history. Reviewers see only your commits, not "merged main into feature" noise.
Step 1: set up a sandbox
mkdir rebase-tutorial && cd rebase-tutorial
git init
echo "v1" > app.txt
git add app.txt && git commit -m "Initial"
git checkout -b feature/login
echo "login" > login.txt
git add login.txt && git commit -m "Add login"
git checkout main
echo "v2" > app.txt
git commit -am "Update app to v2"
Now main has moved on; your feature branch is behind.
Step 2: rebase
git checkout feature/login
git rebase main
Git replays your feature/login commits on top of the new main. The branch now starts from the latest main tip.
Step 3: handle conflicts
If a rebase hits a conflict, Git pauses on the offending commit:
CONFLICT (content): Merge conflict in app.txt
error: could not apply abc1234... Add login
Resolve as you would a merge conflict:
git status # see conflicted files
# ...edit files to resolve...
git add app.txt
git rebase --continue
Repeat for each conflicted commit. To bail out:
git rebase --abort
Step 4: verify
git log --oneline --graph
# main commits, then feature commits, all linear
Step 5: push
The rebase rewrote SHAs. If you had previously pushed the feature branch, a normal push will be rejected:
git push
# ! [rejected] feature/login -> feature/login (non-fast-forward)
Use --force-with-lease:
git push --force-with-lease
This rejects the push if someone else updated the remote since your last fetch - safer than plain --force.
Common variants
# Rebase against the remote main
git fetch origin
git rebase origin/main
# Configure pull to rebase by default
git config --global pull.rebase true
# Update the local main, then rebase
git checkout main && git pull
git checkout feature/login && git rebase main
Interactive rebase
Rebase becomes more powerful interactively:
git rebase -i main
An editor opens with each commit. Mark pick, reword, squash, fixup, edit, or drop. This lets you tidy commits during the rebase.
Pitfalls and rules
- Never rebase shared branches. If others have based work on your branch, rebasing rewrites their parents and creates chaos.
- Use
--force-with-lease, not--force. The former refuses to overwrite unexpected remote changes. - Resolve conflicts in the order they arise. Don't try to resolve "all at once" - rebase replays one commit at a time.
- If conflicts are overwhelming, abort and reconsider. Sometimes a smaller, more frequent rebase produces fewer total conflicts.
The discipline payoff
Rebasing weekly during a long-running branch keeps conflicts small and frequent. Rebasing monthly produces a giant conflict that takes a day to resolve. Treat rebase as routine maintenance, not a special event.
Recovery
A rebase gone wrong is recoverable - the reflog has your back:
git reflog
# find the SHA before "rebase (start)"
git reset --hard ORIG_HEAD
Once you have done this rescue once, rebasing stops feeling dangerous.