Git Course 0%

Rebasing explained; merge or rebase?

Intermediate Git CLI VS Code UI IntelliJ UI GitLab UI GitHub UI ≈ 14 min

What you will learn

  • What rebase actually does, commit by commit
  • Why a rebased branch needs --force-with-lease and what that flag protects
  • The golden rule, and how to decide between rebase and merge for a given branch

After this lesson you can

  • I can rebase my own branch onto main and push the result safely
  • I can say why rebasing a shared branch is a problem, not a preference

Why this matters

Rebase has a reputation: powerful, tidy, and dangerous. All three are true, and the danger is entirely predictable once you know that rebase does not move commits, it re-creates them. This lesson shows the mechanism, the force push it leads to, and the single rule that separates the safe use from the one that ruins a colleague's afternoon.

What rebase does

Rebase Before: main advanced from C3 to C6; the branch docs/12 has C4 and C5 branching from C3. After git rebase main on docs/12: the branch's commits are re-created as C4 prime and C5 prime on top of C6, with new hashes; the old C4 and C5 are left behind; history is a straight line and the branch can fast-forward into main. BEFORE AFTER: git rebase main (on docs/12) C3 C6 C4 C5 main docs/12 C3 C6 C4' C5' C4 C5 main docs/12 the old commits, now on no branch C4' and C5' are new commits, with new hashes and the same changes: history was rewritten
Rebase re-creates each of the branch's commits on top of the new base; the originals are left behind with their old hashes.
Terminal
$ git switch docs/12-windows-install-steps
$ git rebase main
Successfully rebased and updated refs/heads/docs/12-windows-install-steps.
Terminal
$ git log --oneline --graph --all --decorate
* 5f181fc (HEAD -> docs/12-windows-install-steps) docs: add Windows steps to installation guide (#12)
* 1a01760 (origin/main, origin/HEAD, main) feat: add Coast Walk trail (#29)
| * 510f1e0 (origin/docs/12-windows-install-steps) docs: add Windows steps to installation guide (#12)
|/  
* 8fa922e (tag: v1.2.0) Initial import of trailguide

Read that graph carefully, because it contains the whole lesson. There are two commits with the same message: 5f181fc, the new one sitting on top of main, and 510f1e0, the original, still pointed at by origin/docs/12-… on the remote. Your branch now has a history the remote does not share.

The push that follows

Terminal
$ git push
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. If you want to integrate the remote changes,
hint: use 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Git is protecting the remote: your new commit is not a descendant of what is there. Following the hint and running git pull would be a mistake here, because it would merge the old commit back in and leave you with both. What you actually want is to replace the remote branch with your rewritten one:

Terminal
$ git push --force-with-lease
To https://gitlab.com/northwind-trails/trailguide.git
 + 510f1e0...5f181fc docs/12-windows-install-steps -> docs/12-windows-install-steps (forced update)

The + and "forced update" mark a branch whose history was replaced.

The golden rule

Rebase only commits that exist nowhere but your own computer and your own branch.

The reason is mechanical, not stylistic. If a colleague has the old commits and you replace them, their next pull produces a repository containing both versions of your work, and every later merge tangles. That is why:

  • Rebasing a feature branch that is yours onto main: normal and useful, even after pushing, as long as nobody else has checked it out.
  • Rebasing main, a release branch, or any branch two people work on: not a matter of taste. Do not.
  • If in doubt, ask, or merge instead. Merging is never wrong; it only makes the graph less tidy.

Merge or rebase?

Both bring main's newer work into your branch. Choose by what you value and by who else is involved:

Question Merge Rebase
What does history look like? Fork and join, the true shape Straight line, as if you had started today
Do my commits keep their hashes? Yes No, they are re-created
Can I do it after pushing? Yes, plain git push Yes, but needs --force-with-lease
Is someone else on this branch? Fine Do not rebase
Conflicts Resolved once, in one commit Possibly once per commit being replayed
Team rule The safe default Common where the team wants a linear main

A practical compromise many teams use: merge main into your branch while you work, and let the platform squash the branch on merge. You never rebase, and main still ends up linear.

How to do it

Terminal
$ git switch main && git pull
$ git switch docs/12-windows-install-steps
$ git rebase main
$ git push --force-with-lease

If a conflict stops the rebase, Git pauses on the commit it could not apply; resolve, git add, then git rebase --continue, or git rebase --abort to return to where you started (lesson 11.5).

Do and don't

Do

  • Rebase your own unshared branch to keep the merge request clean.
  • Update main immediately before rebasing, so you land on the current base.
  • Use --force-with-lease for the push that follows, and read its output.

Don't

  • Don't rebase main, a release branch, or any branch a colleague has checked out.
  • Don't follow the "use 'git pull' before pushing again" hint after a rebase; it merges the old commits back.
  • Don't rebase to fix a rejected push before checking why it was rejected; a colleague's new commits look the same at first glance.

Common mistakes

  • git pull after rebasing. You end up with both the old and the new commits and a confusing merge. If it happens: git reset --hard origin/<branch> if the remote is right, or lesson 12.1 to recover.
  • Plain --force on a shared branch. Colleagues' commits vanish from the remote. Recoverable through their local copies and the reflog, but a bad afternoon.
  • A rebase with many conflicts. Each replayed commit can conflict separately. git rebase --abort and merge instead; nobody will judge you.
  • Rebasing after the reviewer started reading. The commits they commented on no longer exist, and GitLab and GitHub show the threads as outdated. Rebase before review, not during.

Try it yourself

Goal: rebase a branch, see the hash change, and recognize the rejected push.

  1. From an up-to-date main, create docs/practice-rebase, make one commit, and push it with -u.
  2. Switch to main, make one commit there, push.
  3. Switch back to the branch and note the commit's hash with git log --oneline -1.
  4. Run git rebase main, then git log --oneline -1 again and compare the hash.
  5. Run git push and read the rejection, then git push --force-with-lease.
  6. Run git log --oneline --graph --all --decorate -4.

Expected result: the hash in step 4 differs from step 3 although the message is identical; step 5 is rejected first and then reports a "forced update".

Show solution

The hash changed because the commit has a new parent, and a commit's hash covers its parent (lesson 2.4). The rejection is Git refusing to discard the old commit on the remote without being told explicitly; --force-with-lease tells it, while still checking that nobody else has pushed. Clean up: git switch main, git branch -D docs/practice-rebase, git push origin --delete docs/practice-rebase.

Check yourself

1. After git rebase main, git push is rejected. What is the correct response?
2. Why does a rebased commit have a different hash?
3. Which branch is safe to rebase?

Key terms

Rebase Rewriting history Hash (SHA) Merge Branch