Rebase moves or reapplies commits onto a new base commit. Instead of combining two branches with a merge commit, rebase rewrites the branch so it appears to start from a later point in history.
Suppose feature/login was created from main, but main has advanced since then. Rebasing feature/login onto main replays the feature branch commits on top of the newer main.
git switch feature/login
git rebase mainThis can make history look cleaner and more linear.
Tip: Rebase rewrites commit history. That is powerful, but it means you should be careful with commits other people are already using.
Common reasons to use rebase include:
mainRebase does not add new project content by itself. It changes how commits are arranged in history.
Imagine this history:
main: A â B â Cfeature/login: A â D â EAfter rebasing feature/login onto main, the feature becomes:
main: A â B â Cfeature/login: A â B â C â D' â E'The apostrophes matter. Git usually creates new commit IDs because it is replaying the changes, not simply moving old commits.
Use:
git switch feature/login
git rebase mainIf everything applies cleanly, you get the same code changes on a newer base.
Rebase can produce conflicts just like merge. When that happens, Git pauses and asks you to resolve them.
A typical flow looks like this:
git rebase main
git statusEdit the conflicted file, remove conflict markers, then continue:
git add app.js
git rebase --continueIf another conflict appears, repeat the process.
If you want to stop and return to the original state:
git rebase --abort-iInteractive rebase lets you edit a series of recent commits. It is one of the most useful cleanup tools in Git.
To review the last three commits:
git rebase -i HEAD~3Git opens your editor with something like:
pick a1b2c3d Add login form
pick d4e5f6g Fix typo in login form
pick h7i8j9k Update login button colorYou can change the action for each commit.
pick: keep the commit as isreword: keep the content but edit the messageedit: pause so you can modify the commitsquash: combine this commit with the previous onedrop: remove the commitSuppose your history contains several tiny âfix typoâ commits that belong with the main feature. Interactive rebase can combine them.
Example:
pick a1b2c3d Add login form
squash d4e5f6g Fix typo in login form
squash h7i8j9k Update login button colorAfter saving, Git combines the commits and lets you write a final commit message.
This is useful before opening a pull request because reviewers often prefer a clean, logical commit history.
Note: Squashing is best used on your own unpublished work. Avoid rewriting shared history unless your team explicitly agrees on that workflow.
Both rebase and merge combine lines of development, but they tell the story differently.
| Topic | Merge | Rebase |
|---|---|---|
| History shape | Preserves branch structure | Creates a more linear history |
| New commit IDs | Usually no rewrite of existing commits | Rewrites rebased commits |
| Safety for shared history | Safer | Needs more care |
| Typical use | Integrating branches | Cleaning history or updating a feature branch |
A common safe habit is:
mainExample:
git switch main
git pull
git switch feature/login
git rebase main
git push --force-with-lease--force-with-lease is safer than plain --force because it checks whether the remote changed in unexpected ways.
Rebase replays commits onto a new base, which can make history look cleaner. Interactive rebase is especially useful for rewording, reordering, or squashing recent commits. But because rebase rewrites history, it should be used carefully on shared branches.
If you are unsure, use merge. If you want a polished branch before review, rebase can be a great tool.
Scenario: Your feature branch had commits D and E, and you rebased it onto a newer main with commits A, B, and C.
Question: Why did the tutorial show the rebased branch as A -> B -> C -> D' -> E' instead of keeping the old D and E exactly?
<MCQExercise exercise={{ id: 'git-rebasing-q1', title: 'Exercise 1: Understanding rebased commits', description: 'Identify why rebased commits usually receive new identities.', options: [ { id: 'a', text: 'Because rebase replays the changes on a new base, which usually creates new commit IDs.' }, { id: 'b', text: 'Because Git deletes the original changes and replaces them with merge commits only.' }, { id: 'c', text: 'Because rebasing works only if the feature branch becomes a tag first.' }, { id: 'd', text: 'Because Git must rename commits whenever the branch name contains a slash.' }, ], correctId: 'a', explanation: 'Rebase does not merely move a pointer. It reapplies each commit onto a new base, so the resulting commits are usually new objects with new IDs even if the code changes look similar.', wrongExplanations: { b: 'That describes a different history shape. Rebase is replay, not automatic merge-commit replacement.', c: 'Tags are unrelated to whether a branch can be rebased.', d: 'Branch naming has nothing to do with commit identity changes during rebase.', }, }} />
Scenario: Your feature branch contains Add login form, Fix typo in login form, and Update login button color as three small related commits.
Question: Which interactive rebase action from the tutorial is designed to combine the later two into the first logical commit?
<MCQExercise
exercise={{
id: 'git-rebasing-q2',
title: 'Exercise 2: Cleaning up tiny commits',
description: 'Choose the interactive rebase action that combines related commits.',
options: [
{ id: 'a', text: 'drop' },
{ id: 'b', text: 'reword' },
{ id: 'c', text: 'squash' },
{ id: 'd', text: 'pick' },
],
correctId: 'c',
explanation: 'The tutorial used squash to combine smaller follow-up commits into the previous commit. That is a common way to turn a noisy branch into a cleaner, more reviewable history before opening or updating a pull request.',
wrongExplanations: {
a: 'drop removes a commit entirely, which is different from combining it with another commit.',
b: 'reword changes the message while keeping the commit content separate.',
d: 'pick leaves the commit as-is with no combination at all.',
},
}}
/>
Question: After rebasing your own feature branch onto the latest main, why did the tutorial recommend git push --force-with-lease instead of plain --force?
<MCQExercise
exercise={{
id: 'git-rebasing-q3',
title: 'Exercise 3: Pushing a rebased branch safely',
description: 'Pick the reason force-with-lease is safer after a history rewrite.',
options: [
{ id: 'a', text: 'It converts the rebase into a merge if the branch contains conflicts.' },
{ id: 'b', text: 'It checks whether the remote changed unexpectedly before overwriting it.' },
{ id: 'c', text: 'It automatically restores dropped commits from the reflog on the server.' },
{ id: 'd', text: 'It prevents commit IDs from changing during the rebase.' },
],
correctId: 'b',
explanation: 'Because rebase rewrites branch history, the push usually must replace the remote branch tip. --force-with-lease adds a safety check so you do not overwrite remote work that changed since your last fetch.',
wrongExplanations: {
a: 'It does not change the workflow from rebase to merge.',
c: 'The flag is a safety guard on push, not a remote recovery mechanism.',
d: 'Commit IDs already changed during the rebase. The push flag does not prevent that.',
},
}}
/>
Only if you are the only person working on it. Rebasing creates new commits with new hashes, so the remote branch and your local one diverge and git push is rejected until you force it. Use git push --force-with-lease rather than --force: it refuses the push if someone else has added commits you have not seen, which turns silent data loss into an error message. Never rebase a shared branch such as main. Everyone who has pulled it will be stranded on commits that no longer exist.
Run git rebase -i HEAD~n where n is how many commits back you want to go, then change pick to squash (or s) on every commit except the first. The first stays pick because the others are folded into it. Git then opens an editor to combine the messages. If you only want the first commit's message, use fixup instead of squash to discard the others. Many platforms also offer "Squash and merge" on a pull request, which achieves the same result without rebasing locally.
git rebase --abort returns the branch to exactly where it was before the rebase started, at any point during the process. That is the escape hatch to reach for whenever you are unsure. If the rebase has already completed and you want the old state back, git reflog lists where HEAD has been, and git reset --hard HEAD@{n} moves you to the entry before the rebase. The reflog is why a completed rebase is recoverable and why "I lost my work" is almost never actually true.
Because a rebase replays each commit in turn, so a line you changed in several commits conflicts once per commit. Resolve the file, git add it, then git rebase --continue to move to the next one. Two things help: squashing related commits before rebasing so there are fewer replays, and enabling git config --global rerere.enabled true, which records how you resolved a conflict and reapplies that resolution automatically the next time the identical conflict appears.
It fetches the remote branch and replays your local commits on top of it, instead of creating a merge commit. Without it, a plain git pull on a branch that has moved on both sides produces a "Merge branch 'main' of ..." commit, and a busy repository fills with them. Setting git config --global pull.rebase true makes it the default and keeps history linear. It is safe for your own unpushed commits, which is exactly what git pull is dealing with.
Work with Git remotes confidently: fetch versus pull, fixing a rejected push without force, upstream tracking, pruning branches and origin versus upstream.
Park work in progress with git stash: apply versus pop, stash untracked files, name and inspect stashes, and decide when a branch is the better tool instead.
Undo almost anything in Git: restore files, reset soft, mixed and hard, revert on shared branches, clean untracked files and recover a bad reset with reflog.