DevOps Elastic Hayway
Document

SUBSCRIBE TO GET FULL ACCESS TO THE E-BOOKS FOR FREE 🎁SUBSCRIBE NOW

Professional Dropdown with Icon

SUBSCRIBE NOW TO GET FREE ACCESS TO EBOOKS

GitLesson 13 / 207 min readUpdated September 11, 2026

Git Rebase vs Git Merge: Differences, Examples and Which One to Use

git merge and git rebase both integrate changes from one branch into another, and both are correct. The difference is the history they leave behind: merge preserves what actually happened, including a merge commit; rebase rewrites your commits so they appear to have been made on top of the latest target branch, giving a straight line. Teams argue about this endlessly, so this tutorial shows both on the same repository, explains the trade-offs, and ends with a policy that works for pull-request-based workflows on GitHub, GitLab and Azure Repos.

Prerequisites: Git 2.40+ and the basics from Git branches. All commands work on Linux, macOS and Windows (Git Bash).

Set up a repository to experiment

mkdir rebase-vs-merge && cd rebase-vs-merge
git init -b main
echo "v1" > app.txt && git add . && git commit -m "C1: initial app"
echo "v2" >> app.txt && git commit -am "C2: add v2"

# Feature branch with two commits
git switch -c feature/login
echo "login page" > login.txt && git add . && git commit -m "F1: login page"
echo "login tests" > login_test.txt && git add . && git commit -m "F2: login tests"

# Meanwhile main moves on
git switch main
echo "hotfix" > hotfix.txt && git add . && git commit -m "C3: hotfix"

git log --oneline --graph --all
# * 3c3c3c3 (HEAD -> main) C3: hotfix
# | * f2f2f2f (feature/login) F2: login tests
# | * f1f1f1f F1: login page
# |/
# * 2c2c2c2 C2: add v2
# * 1c1c1c1 C1: initial app

The branches have diverged: main has C3, feature/login has F1 and F2, and they share C2 as the merge base. Let’s integrate the feature both ways.

Option A – git merge

git switch main
git merge feature/login
# Merge made by the 'ort' strategy.

git log --oneline --graph
# *   9a9a9a9 (HEAD -> main) Merge branch 'feature/login'
# |
# | * f2f2f2f (feature/login) F2: login tests
# | * f1f1f1f F1: login page
# * | 3c3c3c3 C3: hotfix
# |/
# * 2c2c2c2 C2: add v2
# * 1c1c1c1 C1: initial app

A new merge commit with two parents ties the histories together. Nothing that existed before was changed: F1 and F2 keep their hashes, which means anyone who already had them is unaffected. The price is a non-linear graph that becomes hard to read with dozens of parallel branches. If main had not moved (no C3), Git would have done a fast-forward instead: simply moving the main pointer to F2 with no merge commit. Use --no-ff to force a merge commit anyway (useful to mark where a feature landed) or --ff-only to refuse a merge when a fast-forward is impossible.

Option B – git rebase

# Undo the merge to try the other way (safe here: nothing was pushed)
git reset --hard 3c3c3c3

git switch feature/login
git rebase main
# Successfully rebased and updated refs/heads/feature/login.

git log --oneline --graph --all
# * e2e2e2e (HEAD -> feature/login) F2: login tests
# * e1e1e1e F1: login page
# * 3c3c3c3 (main) C3: hotfix
# * 2c2c2c2 C2: add v2
# * 1c1c1c1 C1: initial app

# Now main can fast-forward: linear history, no merge commit
git switch main
git merge --ff-only feature/login

Rebase took each feature commit, replayed it on top of C3 and created new commits (e1, e2) with new hashes; the old f1/f2 are now unreferenced. The history reads as if you had started the feature after the hotfix. Clean, linear, easy to git bisect, but it is a rewrite, and that has consequences when the branch is shared.

The golden rule of rebasing

Never rebase a branch that other people have based work on. Rebasing main, develop or any shared branch replaces commits your teammates already have; their next pull produces duplicate commits and conflicts. Rebase your own feature branches; merge into shared ones. If you must rewrite a branch you already pushed (your own PR branch), push with git push --force-with-lease, which refuses to overwrite commits you have not seen.

Handling conflicts

Conflicts happen in both cases when the same lines changed on both sides. The workflow differs slightly:

mergerebase
When conflicts appearOnce, for the whole mergeCommit by commit, as each is replayed
Fix, thengit add <file> → git commitgit add <file> → git rebase --continue
Skip a commitn/agit rebase --skip
Give upgit merge --abortgit rebase --abort
“Ours” and “theirs”ours = current branchreversed: ours = the branch you rebase onto
git rebase main
# CONFLICT (content): Merge conflict in app.txt
git status                     # shows which commit is being replayed
# ... edit app.txt, remove <<<<<<< ======= >>>>>>> markers ...
git add app.txt
git rebase --continue

# Remember resolutions so repeated conflicts resolve automatically
git config --global rerere.enabled true

Interactive rebase: clean up before you open a PR

The killer feature of rebase is -i: rewrite the last N commits of your own branch to squash “fix typo” commits, reorder, reword or drop them, so reviewers see a coherent story.

git rebase -i main            # or: git rebase -i HEAD~4

# Editor opens with one line per commit (oldest first):
# pick e1e1e1e F1: login page
# pick e2e2e2e F2: login tests
# pick a3a3a3a fix typo
# pick b4b4b4b wip
#
# Change to:
# pick   e1e1e1e F1: login page
# squash a3a3a3a fix typo          # melt into F1, edit the message
# pick   e2e2e2e F2: login tests
# fixup  b4b4b4b wip               # melt into F2, discard its message

# Other verbs: reword (edit message), edit (stop to amend), drop, exec (run a command, e.g. tests)

Shortcut: commit fixes with git commit --fixup=<hash>, then git rebase -i --autosquash main arranges them automatically. Set git config --global rebase.autosquash true to make it the default.

git pull: merge or rebase?

By default git pull merges the remote branch into your local one, producing “Merge branch ‘main’ of …” commits that clutter history. Most teams prefer rebasing local commits on top of the remote:

git pull --rebase                       # one-off
git config --global pull.rebase true    # always
git config --global rebase.autoStash true   # stash/unstash dirty work automatically

Merge strategies on the pull request

Hosting platforms offer three buttons that map to what you learned above:

ButtonResult on mainGood for
Merge commit (--no-ff)All PR commits + a merge commitKeeping full history; easy revert of a whole feature (git revert -m 1)
Squash and mergeOne commit per PRSmall PRs, noisy commit habits, very readable main
Rebase and mergePR commits replayed linearly, no merge commitCurated commits (after interactive rebase), bisect-friendly history

A policy that works for most teams

  1. Work on short-lived feature branches; keep them current with git rebase main (or git pull --rebase origin main) instead of merging main into the feature.
  2. Before opening the PR, git rebase -i to squash fixups into meaningful commits; push with --force-with-lease.
  3. Integrate into main through the PR with Squash or Rebase and merge; protect main so nobody force-pushes it.
  4. Never rebase main, release branches, or any branch someone else has checked out.

Recovering from a bad rebase

git reflog                      # every position HEAD had, including before the rebase
# e2e2e2e HEAD@{0}: rebase (finish): returning to refs/heads/feature/login
# f2f2f2f HEAD@{3}: commit: F2: login tests   <- the state before rebase

git reset --hard HEAD@{3}       # branch is exactly as before
# or, non-destructively:
git branch backup-before-rebase f2f2f2f

Rewritten commits are not deleted immediately; they stay reachable through the reflog for at least 30 days, so a mistaken rebase is almost always recoverable.

Key takeaways

  • Merge preserves history and adds a merge commit; rebase rewrites commits to produce a linear history.
  • Rebase your own branches, merge into shared ones; never rebase public branches.
  • Interactive rebase (-i, --autosquash) is how you produce reviewable commits.
  • pull.rebase=true and --force-with-lease make the rebase workflow safe day to day.
  • The reflog is your undo button.

Next tutorial

Next: git stash and git cherry-pick. Official docs: Pro Git – Rebasing, git-rebase reference.

Retour parcours Git — hub de la série et leçons sœurs.

Share your love

Leave a Reply

Your email address will not be published. Required fields are marked *