Git

What Is Git Rebase? A Simple Guide for Developers

What Is Git Rebase? A Simple Guide for Developers

Most people reach for rebase when a feature branch has fallen behind and they’d rather not leave a merge commit in the log. It also gets used to tidy commits before a pull request goes up for review.

The common misconception is that rebasing moves commits somewhere else. According to the Pro Git book, it abandons the existing ones and creates new commits with new hashes.

The final snapshot matches what a merge would produce, so only the history differs.

What is Git rebase?

YouTube player

Rebasing takes the commits you made on a branch and replays them on top of a different base commit. You end up with a linear history and no merge commit.

Read the log of a rebased branch and the work appears to have happened in sequence, even when it happened in parallel (Pro Git).

Git merge goes the other way. It keeps both histories and ties them together with a merge commit.

People mostly use rebase for two things. One is updating a feature branch with whatever landed upstream since you started. The other is reordering, squashing or rewording commits before anyone else sees them. The tidy, in-order log is a side effect of both.

Tutorials disagree on what rebase actually does to commits. GitKraken Learn describes it as cut-and-paste, deleting commits from one branch and adding them to another.

Pro Git is more exact. Rebasing abandons the existing commits and creates new ones that are similar but different. That second description is the one that predicts what goes wrong on shared branches.

Rebase is not a file transfer between branches, and it doesn’t delete anything (the old branch tip stays reachable through the reflog). It’s also a poor default for branches other people use.

Why is GitHub the heart of open source?

Uncover GitHub statistics: developer community growth, repository trends, collaboration patterns, and the platform that powers modern software development.

Explore GitHub Data →

How does Git rebase work?

YouTube player

Git finds the common ancestor of your branch and the new base, saves each of your commits as a diff, resets the branch to the new base, and applies the diffs one at a time. Every replayed commit gets a new hash because its parent changed.

The git-scm.com manual (as of Git 2.54.0, April 2026) spells out what git rebase <upstream> does, in this order:

  1. List every commit on the current branch since it branched off the upstream, skipping commits that already have an equivalent upstream
  2. Check out the upstream, the equivalent of git checkout --detach <upstream>
  3. Replay the listed commits in order, much like running git cherry-pick on each one
  4. Point the branch at the final commit, the equivalent of git checkout -B <branch>

Step two leaves HEAD in a detached state until the last commit lands. That’s why the branch only moves once the whole series has been replayed.

A commit hash covers the parent pointer, so a new parent gives every commit in the series a new hash, even when the diff is identical.

Git also computes a patch-id, a checksum of the diff alone (Pro Git). Read that alongside step one and you can see why a rebase drops commits already present upstream. Equivalence is judged by patch, not by hash.

Looking to sharpen your Git skills? Branching, merging, rebasing, and everything else you need - including git rebase, git stash, and commit workflows - is on one page in the Git Cheat Sheet.

The shortcut fails once the upstream rewrite changed the patch, through a conflict resolution or an interactive edit. The manual calls that the hard case of recovering from an upstream rebase.

Merge backend and apply backend

Rebase runs on the merge backend by default, with the ort strategy (git-scm.com, Git 2.54.0). The apply backend builds patches and feeds them to git am. It’s still there through --apply.

It has real limits. It drops commits that started empty, and commits that become empty, with no option to change that. It also matches hunks by surrounding context, and the manual documents cases of commits reapplied to the wrong place with no conflict reported. Directory rename detection is skipped too.

The manual adds that --apply may become a no-op once the merge backend covers everything it does.

Git rebase commands and options

The basic form is git rebase <upstream>, run on the branch you want to move. Every other option changes which commits move, where they land, or how the rebase pauses and resumes.

Command or optionWhat it doesUse it for
git rebase <upstream>Replays the current branch onto the upstreamUpdating a feature branch
--onto <newbase> <oldbase>Replays a commit range onto a different baseMoving a branch, removing a range
git pull --rebaseFetches, then rebases local commitsAvoiding merge commits on pull
--rebase-mergesRecreates merge commitsKeeping branch topology
--update-refsMoves branches that point at rebased commitsStacked branches
--continue, --skip, --abort, --quitResumes, skips, cancels with reset, cancels without resetStops mid-rebase

Rebasing a branch onto main

Check out the feature branch and run git rebase main. From any other branch, git rebase main feature checks out the feature branch first (git-scm.com).

The upstream argument accepts any valid commit, not only a branch name. Leave it out and Git uses the upstream configured for the current branch, aborting if there isn’t one.

Rebasing onto a specific commit

YouTube player

The --onto form takes two references, the new base and the old base.

Pro Git’s example has client built on server. Running git rebase --onto main server client replays only the client commits onto main and leaves server behind.

It also works for removing commits. In the manual’s example, git rebase --onto topicA~5 topicA~3 topicA drops commits F and G and replays H, I and J.

Pulling with rebase

A plain git pull fetches and then, by default, only fast-forwards (--ff-only). If your local branch has diverged, it fails. --no-rebase makes it run git merge instead, and --rebase makes it run git rebase (git-pull manual).

The fetched upstream becomes the base, and your unpushed local commits are replayed on top of it.

Newer options in the manual

The --update-refs option arrived in Git 2.38 (October 2022). It force-updates any branch that points at a commit being rebased, except branches checked out in a worktree.

It’s aimed at stacked branches, where part two depends on part one. Without it, rewriting part one leaves part two disconnected from history (GitHub blog).

Set rebase.updateRefs to true to make it the default. Adam Johnson warns that the default can pick up branches you didn’t intend to rebase, so read the todo list before saving it.

By default, rebase drops merge commits and flattens everything into one line. --rebase-merges recreates them instead, with one catch from the manual: conflicts you resolved inside those merge commits have to be resolved again by hand.

How does interactive rebase clean up commit history?

YouTube player

Run git rebase -i <commit> and Git opens a todo list of every commit after that point. Change the word at the start of each line and Git applies that action when it replays the commit.

Most developers use this to polish a feature branch before merging it into the main code base, according to Atlassian, which also advises cleaning up before the branch reaches code review. Merge commits stay out of the list unless you pass --rebase-merges.

ActionEffect on the commitEffect on the message
pickKept as isUnchanged
rewordKept as isEditor opens for a new message
editRebase stops after applying it, so you can amend filesYou can amend it
squashFolded into the previous commitMessages concatenated
fixupFolded into the previous commitThis commit’s message discarded
dropRemovedRemoved with it
execRuns a shell command after the line aboveNone

Squashing commits in one session

To turn the last three commits into one:

  1. Run git rebase -i HEAD~3 to list the last three commits
  2. Leave pick on the first line and change the second and third to squash or fixup
  3. Save and close the editor
  4. Edit the combined message when the editor reopens
  5. Run git log to confirm one commit replaced three

When you squash commits this way, the messages are concatenated. Fixup discards them, and fixup -c or -C keeps only the message of that commit (-C skips the editor).

If the commits had different authors, the folded commit goes to the author of the first one (git-scm.com).

Autosquash and exec

Mark a fix while you work with git commit --fixup <commit>. Later, git rebase -i --autosquash moves each “fixup!” commit right after its target and changes its action from pick to fixup.

The manual names that the recommended route. Set rebase.autoSquash to true to apply it by default for interactive rebases.

Reordering creates intermediate commits nobody has run. git rebase -i --exec "make test" appends a test run after every commit and stops on the first failure.

To split one commit, mark it edit and run git reset HEAD^ when the rebase stops. Then stage and commit the pieces before git rebase --continue.

Where interactive rebase goes wrong

YouTube player

Deleting a todo line removes the commit, and rebase.missingCommitsCheck defaults to “ignore” (git-scm.com, Git 2.54.0). Set it to warn or error to catch mistakes. Better still, use drop instead of deleting the line, since drop never triggers the warning.

Rewriting a branch that others have based work on forces them to repair their history by hand. And after a reorder, the intermediate steps are untested.

Git rebase vs merge: which one should you use?

YouTube player

Rebase a feature branch that only you use, merge anything shared, and rebase before the final merge when you want that merge to fast-forward. Atlassian’s tutorial lands on the same rule, and it covers most team setups.

CriterionGit rebaseGit merge
Original commitsReplaced by rewritten copiesKept untouched
Branch traceabilityGone once commits are replayedVisible through the merge commit
Conflict resolutionOnce per conflicting commitOnce, in the merge commit
Safe on shared branchesNoYes
Cleanup before reviewYes, through interactive modeNo equivalent

Pros and cons of each

YouTube player

Rebase gives you a straight line for git bisect and git log when you’re hunting a bug. You also skip the filler merge commit every time you pull upstream work, the final merge can fast-forward, and local commits get cleaned up before anyone reviews them.

The price is rewritten history. Atlassian’s team-workflow article says it demands more care from the whole team, and long branches hit more conflict stops.

Merge is the safest default, per the same Atlassian article. An explicit merge with --no-ff records every integration, and the graph shows where each branch began and ended.

On the downside, pulling upstream into a feature branch adds merge commits that clutter the log, and there’s no way to tidy local commits first.

Does rebase cause more or fewer conflicts?

The sources disagree. Atlassian’s rebase guide warns that conflicts can become more frequent in a rebase workflow, while GeeksforGeeks advises rebasing regularly to cut down future conflicts.

Both hold at different scales. Rebase resolves conflicts commit by commit, and an Atlassian Community answer notes that a conflict you already resolved can come back when you rebase again later.

Small, frequent rebases keep each conflict small. One rebase of a long branch stacks them up.

GitHub Docs describes the same mechanism for squash merges. Keep working on a long-running head branch after squashing and you resolve the same conflicts repeatedly.

What the three sources share is overlapping changes replayed again and again over a base that moved.

What Bitbucket, GitHub and GitLab offer

Since September 30, 2024, Bitbucket Cloud pull requests can sync through a rebase (previously command line only), and merging gained rebase + merge, rebase + fast forward and squash (fast forward only).

GitHub’s Rebase and merge adds commits one by one with no merge commit. It always creates new SHAs and updates committer information, unlike a local git rebase on top of an ancestor commit. It’s unavailable when the pull request has conflicts or when the rebase is judged unsafe (GitHub Docs).

GitLab offers merge commit, merge commit with semi-linear history and fast-forward merge. Automatic rebase before merge reached general availability in GitLab 19.2 and does not re-run pipelines on the rebased result (GitLab Docs, version 19.5).

Whatever the platform offers, write the choice into the team’s git workflow instead of leaving it to whoever clicks the button.

How do you resolve conflicts during a rebase?

YouTube player

When a replayed commit conflicts, Git stops at that commit and leaves conflict markers in the files. Edit the files, stage them with git add, then run git rebase --continue.

  1. Run git diff to find the conflict markers (<<<<<<<) in the stopped commit
  2. Edit each file to the state you want
  3. Stage every resolved file with git add <filename>
  4. Run git rebase --continue
  5. Repeat if the next replayed commit conflicts too

Step five is the catch. A branch where every commit touches the same lines stops once per commit.

The marker syntax and editing steps match what you do when you resolve merge conflicts in Git outside a rebase.

Skip, abort or quit

Use --skip and Git drops the commit that caused the conflict and moves on, so its changes leave the branch. --abort resets HEAD to the original branch and discards the rebase in progress.

--quit stops the rebase but leaves HEAD, the index and the working tree as they are. An autostash entry, if one exists, goes to the stash list (git-scm.com).

Skip is the only one that loses work.

Ours and theirs swap sides

During a rebase, “ours” is the series rebased so far, starting from the upstream, and “theirs” is your working branch. The manual flags the reversal for -m and for -X strategy options.

That trips up anyone who runs -X theirs expecting to keep the upstream side. It also explains the manual’s warning about -s ours, which empties every patch from the branch.

What happens to the commit message

After you resolve a conflict and continue, the merge backend opens an editor so you can update the message. The apply backend reuses the original message without asking (git-scm.com manual, Git 2.54.0).

When should you not use Git rebase?

YouTube player

Skip it on any branch where a teammate may have based work on your commits.

Where the line sits

A branch that never left your machine can be rebased freely (Pro Git). So can one that was pushed but that nobody built on, per Pro Git. A branch pushed publicly and already in use is where the trouble starts.

Atlassian’s golden rule is stricter: never rebase public branches. The two positions differ on the middle case, which is why a team policy has to define who counts as “somebody”.

What breaks downstream

A teammate who merges the rewritten branch ends up with both versions. Pro Git describes two commits with the same author, date and message, and warns that pushing that history back reintroduces the rebased commits.

The manual adds the ripple. Duplicates stay in the history until the topic branches are rebased too, and then everyone downstream of those branches has to rebase as well.

Pushing after a rebase

A rebased branch that was already pushed is a non-fast-forward update. By default git push refuses it, because the remote would lose history (git-push manual, as of Git 2.55.0, June 2026).

Plain --force skips every safety check, the lease check included, and can make the remote lose commits. --force-with-lease is gentler and only overwrites if the remote ref still holds the value you expect. --force-if-includes goes a step further and checks that the remote-tracking tip has been integrated locally.

The bare lease has a hole. A background git fetch from an editor plugin or a cron job updates the remote-tracking ref and defeats the protection, and the manual says so plainly.

Naming the expected value, as in --force-with-lease=<ref>:<expect>, closes it. A server can also refuse the push outright through receive.denyNonFastForwards.

Other wrong-fit cases

Long-lived shared branches, such as a release branch, are a bad match. So are teams that need branch traceability, where Atlassian points to a merge policy. Projects that require signed commits have a problem too, because GitLab’s server-side automatic rebase removes GPG signatures.

When a change on a shared branch has to be undone, git revert adds a new commit instead of rewriting old ones.

Not comfortable yet? Rebase a temporary copy of the branch first, as Atlassian suggests.

How do you undo a Git rebase?

Find where the branch pointed before the rebase in the reflog, then reset the branch to that commit. An unfinished rebase is simpler, since git rebase --abort restores the original branch.

  1. Run git reflog and find the entry from just before the rebase started
  2. Check it with git show <entry> to confirm it is the old tip
  3. Run git reset --hard <entry> on the rebased branch
  4. Run git log to confirm the original commits are back

The manual notes that the previous branch tip is reachable through the branch reflog as @{1}. Using git reset with --hard also overwrites uncommitted changes, so commit or stash them first.

The ORIG\_HEAD shortcut

Git sets ORIG\_HEAD to the branch tip when the rebase starts, so git reset --hard ORIG_HEAD works right after a rebase.

It can fail, though. The manual warns that ORIG\_HEAD is not guaranteed to survive if another command that changes it, git reset among them, ran in between.

How long the reflog keeps the old tip

Reflog entries expire after 90 days by default, and entries whose commits are no longer reachable from the branch tip expire after 30 days (git-reflog manual, Git 2.51.1, October 2025).

After a rebase the old tip is unreachable from the new one, which puts it on the shorter clock. The settings are gc.reflogExpire and gc.reflogExpireUnreachable, and git gc applies them.

Undoing a rebase you already pushed

Resetting the local branch does not touch the remote, which still holds the rebased history. Pushing the old tip back is another non-fast-forward update, so it needs --force-with-lease or --force.

A teammate who already fetched the rewritten branch moves their own commits onto the new tip with git rebase --onto subsystem subsystem@{1}. That’s the manual’s recovery example, where subsystem is the rewritten branch.

Git Rebase FAQ

Can I make git pull use rebase by default?

Yes. Run git config –global pull.rebase true and every plain pull fetches, then rebases your local commits onto the upstream (Pro Git).

The pull manual also accepts a value of merges, which keeps your local merge commits through rebase-merges.

What does git rerere do during a rebase?

It remembers how you resolved a conflict and reuses that resolution when the same conflict comes back.

That matters in a rebase, where one overlap can stop the replay several times. The option rerere-autoupdate also stages the reused result for you.

Can I rebase with uncommitted changes?

Not by default. Pass –autostash and Git stashes before the rebase and reapplies afterward, or set rebase.autoStash to true.

One catch from the git-scm.com manual: the final stash application can produce conflicts of its own.

How is Git rebase different from cherry-pick?

Cherry-pick copies the commits you name onto your current branch. Rebase replays a whole series and then moves the branch to the result.

The manual describes the replay step as similar to running cherry-pick on each commit in turn.

Is Git rebase the same as git reset?

No. Reset moves a branch tip to a commit you name without replaying anything, while rebase rebuilds your commits on a new base.

The two meet in the manual’s commit-splitting recipe: stop with edit, run git reset HEAD^, then commit the pieces separately.

Does the new git history command replace rebase?

Not yet. The git-history manual lists drop, fixup, reword and split, but it does not work with histories that contain merges.

It also refuses operations that can end in merge conflicts. For those cases the manual sends you back to rebase with rebase-merges.

What to Set Up Before Your Next Git Rebase

YouTube player

Rebase stays safe on a team when a written branch policy and a guarded force push exist before the first rebase runs.

Name the branches that can be rebased in the team policy, set push.useForceIfIncludes to true, and keep interactive cleanup to local commits.

The order matters because no setting can tell a private branch from a shared one, so policy has to come first.

Pick one lease style. The git-push manual makes force-if-includes a no-op when it’s paired with an explicit expected value, so the config setting only protects the bare lease forms (Git 2.55.0, June 2026).

The manual still labels those bare forms experimental, so recheck this setup after each Git release. A git cheat sheet keeps the flags within reach until they become habit.

Bogdan Sandu

Stay sharp. Ship better code.

Every week: one curated article, one tool worth knowing, one tip you can use tomorrow. No noise, no padding.