Rebase onto main or master
Run rebase from the branch you want to move. It replays that branch's commits on top of the target.
| Command | What it does |
|---|---|
| Replay the current branch's commits on top of main | |
| Same, on repos whose default branch is master | |
| Update the remote-tracking branches first | |
| Rebase onto the remote's latest main, no local checkout of main needed | |
| Check out feature, then rebase it onto main | |
| Pull, replaying your local commits on top instead of merging | |
| Make every pull a rebase by default | |
| Stash uncommitted changes first, restore them after | |
| Move stacked branches along with this one (Git 2.38+) | |
| Push the rewritten branch; refuses if the remote grew new commits | |
| Stop and put the branch back exactly as it was |
The full update loop, step by step:
git switch feature
git fetch origin
git rebase origin/main # if it stops on a conflict: fix, git add, git rebase --continue
git push --force-with-lease # the commits were rewritten, so a plain push is rejectedRebase After a Pull
When your branch and its remote have both moved, Git stops with "You have divergent branches and need to specify how to reconcile them". Rebasing puts your local commits back on top instead of making a merge commit.
| Command | What it does |
|---|---|
| Fetch, then replay your local commits on top of the remote's | |
| Make every pull a rebase, which clears the divergent branches error | |
| Keep your own merge commits instead of flattening them | |
| Stash and restore uncommitted work around every rebase | |
| Re-base a branch after upstream squash-merged part of it | |
| Rebase without pulling, when you already ran |
# hint: You have divergent branches and need to specify how to reconcile them.
git config --global pull.rebase true # set it once
git pull # now rebases instead of mergingAfter someone squash-merges your pull request, the branch's old commits no longer match anything upstream, so a plain rebase replays them as duplicates. Move only the commits you added since then:
git rebase --onto origin/main <last-commit-that-got-squashed> featureInteractive Rebase
opens a todo list of commits, oldest first. Change the word in front of a commit to change what happens to it; reorder the lines to reorder the commits.
| Command | What it does |
|---|---|
| Edit the last 5 commits | |
| Edit every commit on this branch that main does not have | |
| Edit from commit abc1234 (included) up to HEAD | |
| Edit every commit on the branch, including the very first one | |
| Replay the branch's merge commits instead of flattening them | |
| Reopen the todo list mid-rebase | |
| Preview which commits the todo will contain |
| Todo action | Effect |
|---|---|
| () | Keep the commit as is |
| () | Keep it, but stop to change the message |
| () | Stop after this commit to amend or split it |
| () | Meld into the commit above, combining both messages |
| () | Meld into the commit above, discarding this message |
| Meld into the commit above, keeping this message instead | |
| () | Delete the commit (deleting the line does the same) |
| () | Run a shell command at this point in the replay |
| () | Pause here; resume with |
A typical cleanup todo before opening a pull request:
pick a1b2c3d Add login endpoint
fixup e4f5a6b fix typo
reword 7c8d9e0 Add rate limiting
drop f0a1b2c WIPRebase Editor
The todo list opens in your sequence editor, which defaults to and then to vim. Saving an empty todo, or quitting with an error, cancels the rebase.
| Command | What it does |
|---|---|
| Use VS Code for commit messages and the todo list | |
| Use a different editor for the todo list only | |
| Swap vim for nano everywhere | |
| Accept the todo as generated, with no editor at all | |
| In vim: save the todo and start the rebase | |
| In vim: quit with an error, which cancels the rebase |
is not optional for a GUI editor. Without it the command returns immediately, Git sees an unchanged todo, and the rebase runs before you have edited anything.
Fixup, Squash & Reword
Mark a commit as a fix the moment you make it, and files it under its target in the todo for you.
| Command | What it does |
|---|---|
| Commit staged changes as a fix for commit abc1234 | |
| Same, but stop to combine the messages during the rebase | |
| Fix abc1234 and reword its message in one go | |
| Sort / commits under their targets automatically | |
| Autosquash on every interactive rebase | |
| Fold staged changes into the newest commit, no rebase needed | |
| Same, keeping the existing message |
# Fix a typo that lives three commits back
git add src/auth.js
git commit --fixup=abc1234
git rebase -i --autosquash main # the fixup line is already in place; save and quitSquash Commits
To squash the last N commits into one, rebase over them interactively and keep only the first .
| Command | What it does |
|---|---|
| Squash the last 3: keep the first , mark the rest or | |
| Un-commit the last 3, keeping all their changes staged | |
| Squash everything the branch has added since main | |
| Collapse the whole branch into one staged change, no todo list | |
| From main: land the whole branch as a single commit |
git rebase -i HEAD~3pick a1b2c3d Add password reset
fixup e4f5a6b fix failing test
fixup 7c8d9e0 review feedbackThe reset route gets the same result without touching the rebase machinery:
git reset --soft HEAD~3
git commit -m "Add password reset"With Conflicts
A rebase replays commits one at a time, so it can stop on any of them. Fix the files, stage them, continue. Never run yourself mid-rebase; commits for you.
| Command | What it does |
|---|---|
| List the conflicted files ("both modified") | |
| Just the paths still unresolved | |
| Show the remaining conflict markers | |
| Mark a fixed file as resolved | |
| Commit the resolution and replay the next commit | |
| Walk the conflicts in your configured merge tool | |
| Show the commit the rebase is stuck on | |
| Redo the markers with the common ancestor shown between them |
The whole loop:
git rebase main
# CONFLICT (content): Merge conflict in src/app.js
vim src/app.js # remove the <<<<<<< ======= >>>>>>> markers
git add src/app.js
git rebase --continue # repeat until "Successfully rebased"Abort, Continue, Skip
after staging fixes, when the commit is already upstream, when the whole thing went sideways.
| Command | What it does |
|---|---|
| Resume after resolving conflicts or an stop | |
| Cancel the rebase and restore the branch as it was | |
| Drop the commit being replayed and move on | |
| Forget the rebase state but leave the files as they are now |
If says there is nothing to commit, the resolution made the commit empty, usually because the change already exists upstream. That is what is for.
Ours vs Theirs
During a rebase, ours and theirs are the reverse of what most people expect. Rebase checks out the branch you are rebasing onto and replays your commits against it, so is that base branch (main) and is your own commit.
| Command | What it does |
|---|---|
| Take the version from the branch you are rebasing onto | |
| Take your own branch's version | |
| Accept your version of every file conflicted at this stop | |
| Accept the base branch's version of every conflicted file | |
| Auto-resolve every conflict in favor of main | |
| Auto-resolve every conflict in favor of your commits |
Editors rename the same two sides. In VS Code's conflict view during a rebase, "Current Change" is the base branch and "Incoming Change" is the commit of yours being replayed.
After a / checkout, the file still needs and .
# Keep your version of the lock file for every conflict in this rebase
git rebase -X theirs main
# Or decide per file, mid-conflict
git checkout --theirs -- package-lock.json
git add package-lock.json
git rebase --continueRebase --onto
moves a range of commits to a new base: . Read it as "take the commits after old-base up to branch, and replay them on new-base".
| Command | What it does |
|---|---|
| Move feature's own commits from old-base onto main | |
| Re-parent featureB from featureA onto main | |
| Move a branch started from the wrong base | |
| Drop the two commits between HEAD |
# You branched featureB off featureA, and featureA just merged.
# Replay only featureB's own commits onto main:
git rebase --onto main featureA featureBEdit a Commit
Mark a commit in the todo and the rebase stops right after applying it, with the commit as HEAD.
| Command | What it does |
|---|---|
| Open a todo that includes commit abc1234; mark it | |
| Change the stopped commit's files or message | |
| Un-commit the stopped commit so you can split it into several | |
| Replay the rest of the branch on top of the change |
# Change a file inside an older commit
git rebase -i abc1234^ # change "pick abc1234" to "edit abc1234"
vim src/config.js
git add src/config.js
git commit --amend --no-edit
git rebase --continueTo split it instead: at the stop, then stage and commit the pieces separately before .
Exec
runs a command after a commit is applied and stops the rebase if it fails, which pinpoints the commit that broke things.
| Command | What it does |
|---|---|
| Run the tests after replaying each commit | |
| The same as a hand-placed todo line | |
| Combine a test run with normal todo editing |
# Find the commit that broke the tests while rebasing a branch
git rebase -x "npm test" main
# stops at the first failing commit: fix it, git commit --amend, git rebase --continueRebase vs Merge
Both integrate one branch into another; they leave different history behind.
| History | Linear, commits rewritten | Merge commit added, shape preserved |
| Commit hashes | Change | Unchanged |
| Safe on a shared branch | No | Yes |
| Conflicts | Per replayed commit | Once, all at the same time |
| Push afterwards | Plain |
Rule of thumb: rebase your own feature branch to keep it current, merge to land finished work into a branch other people use. The git cheat sheet covers the merge side in full.
Rebase on GitHub
GitHub's "Rebase and merge" button replays a pull request's commits onto the base branch with no merge commit, rewriting them with new SHAs. Both it and the "Update branch" dropdown need a clean replay; on a conflict you do it locally and push.
| Command | What it does |
|---|---|
| Pick up the base branch before updating the PR | |
| The local equivalent of GitHub's "Update with rebase" | |
| Update the open pull request with the rebased commits | |
| Check out someone else's PR branch to rebase it | |
| Tidy the PR's commits before asking for review |
# "This branch is out-of-date with the base branch" on a PR with conflicts
git fetch origin
git rebase origin/main # fix, git add, git rebase --continue
git push --force-with-lease # the PR updates in place, review comments stayForce-pushing a rebased branch keeps the pull request and its conversation; only the diffs of individual commits become unreachable, so old review comments may show as outdated.
Cherry-pick vs Rebase
copies commits and leaves the originals alone, so the change exists in two places. Rebase moves a range onto a new base and abandons the originals. See the git cheat sheet for the rest of cherry-pick.
| Command | What it does |
|---|---|
| Copy one commit onto the current branch | |
| Copy a range of commits, both ends included | |
| Copy the changes but stop before committing | |
| Same conflict loop as a rebase | |
| Move a whole range instead of copying it commit by commit |
# One fix belongs on the release branch too: copy it
git switch release-2.1
git cherry-pick abc1234
# The whole branch started from the wrong base: move it
git rebase --onto main old-base featureUndo a Rebase
The old commits are not gone after a rebase; the reflog still points at them for about 90 days.
| Command | What it does |
|---|---|
| Mid-rebase: stop and restore everything | |
| Every position HEAD has held, rebased-away commits included | |
| Move the branch back to reflog entry 7 | |
| Jump straight back, if nothing has overwritten ORIG_HEAD since | |
| The same history, scoped to one branch |
git reflog
# a1b2c3d HEAD@{0}: rebase (finish): returning to refs/heads/feature
# ...
# 9f8e7d6 HEAD@{7}: commit: the tip before the rebase started
git reset --hard HEAD@{7}Pick the entry just below the first line: that was the branch tip before anything was rewritten.
Gotchas
- Never rebase commits other people have already pulled. Rewriting a shared branch forces everyone else into a painful recovery; merge on shared branches, rebase on your own.
- After any rebase, a plain is rejected. Use , never bare : with-lease fails safely if a teammate pushed in the meantime.
- Ours and theirs swap meaning during a rebase. is the branch you are rebasing onto, is your own commit. and swap the same way.
- Do not after fixing a conflict; then is the whole move. Committing by hand mid-rebase leads to duplicated or empty commits.
- Rebase refuses to start over uncommitted changes. Commit them, stash them, or pass (or set once).