Rebasing a feature branch onto main replays your commits on top of the latest upstream work — giving you a clean, linear history without a messy merge commit. By the end of this guide your feature branch will sit cleanly on top of the current main tip, every one of your commits intact and ready to merge or open a pull request.
Prerequisites
- Git 2.23 or later installed (
git --versionto check) - A local clone of the repository with both
mainand your feature branch present - No uncommitted changes on your feature branch (or they are stashed)
- You understand that rebasing rewrites commit SHAs — never rebase a branch others are actively working on
Step 1 — Stash Any Uncommitted Work
Rebase requires a clean working tree. If you have changes in progress that are not yet committed, stash them first so nothing gets lost.
git stash push -m "wip: before rebase onto main"
You will restore these at the end with git stash pop. If your tree is already clean, skip this step.
Step 2 — Fetch the Latest Changes from Origin
Before you rebase, pull down everything that has landed on main since you branched off. Rebasing against a stale local main defeats the purpose.
git fetch origin
This updates origin/main (and all other remote refs) without touching your local branches. You are not switching branches yet.
Step 3 — Check Out Your Feature Branch
Make sure you are on the branch you want to rebase, not on main.
git checkout feature/my-feature
Confirm with git branch — the asterisk should sit next to your feature branch name.
Step 4 — Run the Rebase
Now replay your commits on top of the latest origin/main. Using origin/main directly (rather than local main) means you are working against the freshest upstream state.
git rebase origin/main
Git walks through each of your commits in order, pausing only if a conflict is detected. If there are no conflicts, the rebase completes immediately and you can jump to Step 7.
Step 5 — Resolve Conflicts One Commit at a Time
If Git pauses with a conflict message, do not panic. Rebase surfaces conflicts commit-by-commit, so you fix a small, focused diff each time rather than one giant merge conflict.
- Open the conflicted file(s). Look for the standard conflict markers (
<<<<<<<,=======,>>>>>>>) and edit the file to the correct final state. - Stage the resolved file:
git add path/to/resolved-file.ext - Continue the rebase — do NOT use
git commithere:git rebase --continue
Repeat for every paused commit. At any point you can bail out completely and return to your pre-rebase state with:
git rebase --abort
Gotcha: Running
git commitinstead ofgit rebase --continueis the most common mistake. It creates a stray commit in the middle of your history. Always use--continueafter staging resolved files.
Step 6 — Verify the Result Locally
Before you push, confirm the commit log looks exactly the way you expect. Your commits should appear on top of the latest main commits with no duplicates.
git log --oneline --graph origin/main..HEAD
This shows only the commits that are on your branch but not yet on origin/main. Cross-check that the count matches the number of commits you wrote. If anything looks off, use git reflog to find the pre-rebase state and recover it.
Step 7 — Force-Push the Rebased Branch
Because rebasing rewrites commit SHAs, a normal git push will be rejected. You must force-push. Use the safer --force-with-lease flag instead of -f — it refuses to overwrite if someone else has pushed to the same branch since your last fetch, protecting against accidental data loss.
git push origin feature/my-feature --force-with-lease
Important: Only force-push branches that belong solely to you. If teammates have checked out your feature branch and based work on it, force-pushing will break their local history. Coordinate before pushing.
Step 8 — Restore Stashed Work
If you stashed changes in Step 1, bring them back now.
git stash pop
If the stash conflicts with the rebased branch state, Git will flag it. Resolve those conflicts the same way you resolved rebase conflicts: edit, git add, done.
Wrap-up
You now have a clean, linear history with your feature branch sitting directly on top of the current main tip — all your commits intact. The full flow in one mental model: fetch → checkout feature → rebase onto origin/main → fix any conflicts commit-by-commit → verify the log → force-push with lease. Make this part of your daily Git workflow and pull requests become trivial to review: no merge noise, no diverged histories, just your changes on top of the latest code.