A rebase changes the base of your branch. It takes the commits on your branch, detaches them, moves the branch pointer to a new starting point, and replays each commit one by one on top of that new base.
The formal definition — "change the base of your branch" — is technically correct but not very helpful. A more practical way to think about it:
A rebase makes it look like you started your work today, even if you actually branched off weeks ago.
Suppose you're on topic and you run git rebase main:
Before rebase:
D ← E ← topic, HEAD
/
A ← B ← C ← F ← G ← main
Step 1: Git walks backwards from topic (E → D) and computes the
diff (patch) for each commit. Stores them in temporary files.
Step 2: Git moves the topic pointer to where main is (commit G).
Commits D and E become unreachable.
D ← E (orphaned — no pointer)
/
A ← B ← C ← F ← G ← main, topic, HEAD
Step 3: Git replays each patch, one by one, creating NEW commits.
A ← B ← C ← F ← G ← D' ← E' ← topic, HEAD
↑
main
After rebase:
- D' and E' have the SAME changes as D and E
- But they are DIFFERENT commits (different SHA hashes)
- The original D and E are orphaned (eventually garbage collected)
- The history is now linear
Remember from Module 3: a commit's SHA hash is computed from its content, its parent, the author, and the timestamp. When you rebase, the parent changes (D' now points to G instead of B), so the hash must be different. The changes (diffs) are the same, but the commits are entirely new objects in Git's database.
Integrating a finished feature into main (the final merge)
You need to preserve the exact history for audit or compliance
Multiple people are working on the same branch
Use rebase when:
Keeping your topic branch up to date with main while you work
Cleaning up your commit history before creating a PR
You want git log --oneline to tell a clear story
The common pattern: Rebase while developing, merge when done.
# While working on your feature (regularly):git fetch origingit rebase origin/main# When feature is complete (once):git switch maingit merge --no-ff topic
Never rebase commits that have been pushed to a shared branch that others are working on.
This is the single most important rule in Git. Here's why:
You and Alice both have:
A ← B ← C ← main, origin/main
You rebase and force push:
A ← B ← C' ← main (your version — C is gone)
Alice still has:
A ← B ← C ← main (her version — C is still there)
Alice does git pull:
A ← B ← C ← M ← main
↖ ↗
C'──┘
Alice now has BOTH versions of C — chaos.
When you rebase, the old commits still exist in other people's clones. When they pull, Git tries to merge your rewritten history with their copy of the original history. The result is duplicate commits, confusion, and broken trust.
There's one exception: your own topic branch that nobody else has checked out.
# You're working alone on feature-logingit rebase maingit push --force-with-lease # Safe: checks that no one else pushed
In practice, this is the normal workflow. You rebase your personal branch, force push it, and nobody is affected. The danger only arises when you rebase main, develop, or any branch that other developers actively pull from.
X ← Y ← M ← topic
/ /
A ← B ← C ← D ← E ← F ← G ← main
Every time you sync with main, another merge commit appears. After a week of regular syncing, your PR has more merge commits than actual work commits. Reviewers hate this.
# 1. Make sure main is up to dategit switch maingit pull# 2. Switch back to your topic branchgit switch topic# 3. Rebase onto maingit rebase main# 4. If conflicts arise, resolve them (see Section 5)# 5. Force push your updated branch (if previously pushed)git push --force-with-lease
During a rebase, Git replays commits sequentially. Each commit might conflict. You fix conflicts for commit D', then continue, and commit E' might also conflict.
Replaying commit D' ... CONFLICT!
→ Resolve → git add → git rebase --continue
Replaying commit E' ... CONFLICT!
→ Resolve → git add → git rebase --continue
Done.
This is actually an advantage: each conflict is smaller and more focused, because you're looking at only one commit's changes at a time.
# 1. Start the rebasegit rebase main# If a conflict occurs:# 2. Check what's conflictedgit status# rebase in progress; onto abc1234# You are currently rebasing branch 'topic' on 'abc1234'.# fix conflicts and then run "git rebase --continue"## Unmerged paths:# both modified: app.py# 3. Open your merge tool (or edit manually)git mergetool# Or open the file, look for <<<<<<< / ======= / >>>>>>> markers# 4. After resolving, stage the filegit add app.py# 5. Continue the rebasegit rebase --continue# If more commits conflict, repeat steps 2-5
Cancel the entire rebase and return to the original state
git rebase --skip
Skip the current commit entirely (rarely needed)
--abort is your panic button. It restores your branch to exactly where it was before the rebase started. No harm done.
--skip is for edge cases — for example, if after resolving conflicts the commit would be empty (because the same change was already applied upstream), you can skip it.
During a merge, "ours" = the branch you're on, "theirs" = the branch you're merging in. During a rebase, the labels are swapped:
During rebase of topic onto main:
"ours" = main (the new base you're replaying onto)
"theirs" = topic (the commit being replayed)
This catches everyone off guard the first time. Remember: during a rebase, Git has already reset to main (step 2 of the mechanics). So "ours" is main, and each replayed commit is "theirs."
When you run git pull on a branch that's behind its upstream, Git has to reconcile the divergence. By default, it merges — creating unnecessary merge commits in your local history.
# Default: git pull = fetch + merge# If local and remote diverged, creates a merge commit# Better: git pull --rebase = fetch + rebase# Replays your local commits on top of the remote
Before pull (diverged):
Local: A ← B ← X ← main, HEAD
Remote: A ← B ← C ← D ← origin/main
git pull (merge):
A ← B ← X ← M ← main
↖ ↗
C ← D ← origin/main
git pull --rebase:
A ← B ← C ← D ← X' ← main
↑
origin/main
The rebase version is cleaner. Your local commit X is replayed on top of the remote changes, as if you had waited for C and D before writing X.
# Make all future pulls use rebasegit config --global pull.rebase true# Or the safest option: fail if fast-forward isn't possiblegit config --global pull.ff only
With pull.rebase true, every git pull automatically becomes git pull --rebase. You can override on a case-by-case basis with git pull --no-rebase.
To appreciate rebase, consider the alternative: using only merges to keep a topic branch up to date.
Without rebase (merge main into topic repeatedly):
X ── M₁ ── Y ── M₂ ── Z ← topic
/ / /
A ← B ← C ← D ←──── E ← F ← G ← main
With rebase (rebase topic onto main before final merge):
A ← B ← C ← D ← E ← F ← G ── X' ── Y' ── Z' ← topic
↑
main
The merge-based approach creates a tangled web. Every sync adds a merge commit. The PR diff includes noise from the merge commits. git log --oneline --graph becomes unreadable.
The rebase-based approach keeps the topic branch as a clean, linear series of commits on top of the latest main. The PR shows only your actual changes. Reviewers can step through your commits in logical order.
git switch maingit switch -c feature-beta# Modify line 1 (same file, same area as what main will change)sed -i '' 's/line 1: shared foundation/line 1: beta foundation/' app.txtgit add app.txtgit commit -m "Beta: update foundation"
Step 11 — Merge main into feature-gamma (instead of rebasing)
git switch feature-gammagit merge main
Resolve the conflict, then:
git add app.txtgit commit
Step 12 — Compare the graphs
git log --oneline --graph --all
Checkpoint: The gamma branch shows the classic diamond merge pattern (diverge → merge commit). The beta branch (from Part B) is a clean straight line. This is the visual difference between merge and rebase.
Create a repository with a main branch and three topic branches (feature-1, feature-2, feature-3) that all diverge from the same commit on main. Advance main with two commits. Rebase all three features onto the updated main, one at a time, merging each into main with --no-ff before rebasing the next. End with a clean sequential graph as if the three developers waited for each other.
Rebase early, rebase often — The longer your branch diverges from main, the more conflicts you'll face. Rebasing daily keeps each resolution trivial.
Rebase onto the remote directly to save time:
git fetch origingit rebase origin/main
No need to switch to main and pull first.
Use git log to preview what will be replayed:
git log --oneline main..topic
This shows exactly the commits that rebase will replay.
Recover from a bad rebase with reflog:
git reflog# Find the SHA of topic before the rebasegit reset --hard <sha>
The team convention matters more than the tool. Some teams use merge-only workflows, some use rebase-only. Neither is wrong. What's wrong is mixing them inconsistently. Agree on a strategy and stick to it.
Rebase before creating your PR, then merge the PR with --no-ff. This gives you the best of both worlds: clean commit history within the PR, plus a merge commit on main that marks where the feature was integrated.
Q1: In your own words, describe what git rebase main does when you're on a topic branch.
Answer
Git identifies the commits unique to your topic branch (since it diverged from main), temporarily saves their diffs, moves the topic branch pointer to where main is, then replays each saved diff on top, creating new commits. The result is that your topic branch appears to have been started from the tip of main.
Q2: After a rebase, why do the commit SHAs change even though the code changes are the same?
Answer
A commit's SHA is computed from its content, parent pointer, author info, and timestamp. After a rebase, each replayed commit has a different parent (it now points to the new base instead of the old one). A different parent means a different input to the hash function, which produces a different SHA. The code diff is the same, but the commit object is different.
Q3: What is the golden rule of rebase?
Answer
Never rebase commits that have been pushed to a shared branch that others are working on. Rebase rewrites history (creates new commit SHAs), and if others have pulled the old commits, they'll end up with duplicates when they pull your rewritten version.
Q4: During a rebase conflict, what does "ours" refer to?
Answer
During a rebase, "ours" refers to the base branch (e.g., main) — the branch you're rebasing onto. This is the opposite of merge, where "ours" is the branch you're currently on. The swap happens because rebase first resets to the base branch, then replays your commits as "theirs."
Q5: You're working on feature-x and need to incorporate the latest changes from main. Write the commands for both the merge approach and the rebase approach.
Answer
Merge approach:
git switch feature-xgit merge main
Rebase approach:
git switch feature-xgit rebase main# (then, if previously pushed:)git push --force-with-lease
Q6: What does git rebase --abort do? When would you use it?
Answer
git rebase --abort cancels the in-progress rebase and restores your branch to exactly the state it was in before the rebase started. You use it when you encounter unexpected conflicts and want to start over, or when you realize you're rebasing the wrong branch.
Q7: Why is git pull --rebase generally preferred over git pull (with merge)?
Answer
git pull --rebase replays your local commits on top of the remote changes, creating a clean linear history. Plain git pull creates a merge commit every time local and remote have diverged, cluttering the log with unnecessary merge commits that have no semantic meaning (they just record a sync point).
Q8: You rebase your feature branch and now git push is rejected. Why? What should you do?
Answer
git push is rejected because rebase rewrote history — the local commits have different SHAs than the ones on the remote. Git sees this as a non-fast-forward push and refuses it to protect you from losing work. You should use git push --force-with-lease, which is safe because it verifies no one else pushed to your branch since your last fetch.
Q9: True or false: a rebase can produce more conflict resolution steps than a merge for the same set of changes.
Answer
True. A merge combines all divergent changes into a single resolution step. A rebase replays commits one at a time, and each commit can produce its own conflict. If you have 5 commits on your branch, you might have to resolve conflicts 5 separate times. However, each individual resolution is typically smaller and easier to reason about.
Q10: Explain the recommended workflow: "rebase while developing, merge when done."
Answer
While you're actively working on a feature branch, regularly rebase it onto main to stay up to date. This keeps your branch clean and minimizes conflict size. When your feature is complete and the PR is approved, merge it into main with --no-ff. The final merge commit serves as a marker in history showing when the feature was integrated, while the individual commits within the branch tell the story of how it was built.