| |

Git 4a 🧩 Rebasing

⏱️ Estimated read: 11 min | |

Rebasing rewrites commit history by replaying your branch’s commits on top of another branch. Where merge creates a merge commit and preserves both branches, rebase produces a linear history — as if you’d always been working from the latest main.

Key point: Rebase rewrites history. That’s its power and its danger. It gives you a clean, linear commit log. But it also means the commits are new — new hashes, new identities. Never rebase a branch others are working on.


What rebasing is

git rebase [branch-name] takes all commits from your current branch that aren’t in the target branch and reapplies them on top of the target’s latest commits.

It’s not a merge. It’s a replay. Each of your commits is applied to the new base, one at a time.

Visual example:

Before:
main:    A---B---C---D
                \
feature:         E---F---G

After git checkout feature && git rebase main:
main:    A---B---C---D
                      \
feature:               E'---F'---G'

E', F', and G' are the same changes as E, F, and G, but they’re new commits applied on top of D. The original commits are gone from the branch’s history.

Why the commits are different: A commit hash is derived from its content, parent, author, and timestamp. Changing the parent (by rebasing onto a new base) changes the hash. The code changes are the same, but they’re new commits.


Rebase vs merge

Both integrate work from one branch into another. They produce different histories.

AspectRebaseMerge
HistoryLinearPreserves branches
Commit hashesNewSame
Merge commitNoneYes
ReadabilityCleanerMore accurate
SafetyRewrites historyNon-destructive

When to use rebase:

  • For feature branches before merging into main
  • When you want a cleaner, linear commit history
  • To keep your branch up-to-date with main

When to use merge:

  • When you want to preserve the actual history of when merges occurred
  • When working on shared branches others are using
  • For historical records showing where features were integrated

Why the trade-off matters: Rebase gives clean history at the cost of accuracy. Merge preserves accuracy at the cost of clutter. Neither is universally right — pick based on whether history fidelity or readability matters more for the branch.


Rebase example

A full walkthrough.

1. Create and switch to main

git checkout main
git pull origin main

Start from the latest main.

2. Create a feature branch

git checkout -b feature/new-login

3. Make commits on the feature branch

# Edit file1.js and commit
git add file1.js
git commit -m "Add login form validation"

# Edit file2.js and commit
git add file2.js
git commit -m "Implement password strength check"

Two commits on the feature branch.

4. Main gets updated meanwhile

git checkout main
git pull origin main  # New commits added to main
git checkout feature/new-login

Someone else pushed to main. Your branch is now based on an older version.

5. Rebase the feature branch onto latest main

git rebase main

Git takes your two commits, rewinds them, and reapplies them on top of the latest main.

6. History now

main:    A---B---C---D---E---F
                            \
feature:                     G'---H'

Your feature branch now starts from the latest main. Commits are new (G', H'), but the changes are preserved.

7. Merge into main

git checkout main
git merge feature/new-login

Because the feature is based on the latest main, the merge is a fast-forward — clean, linear history.

Why this workflow: Rebasing before merging gives you a linear history without merge commits. If you rebase first and then merge, main stays clean.


Interactive rebase

git rebase -i opens an editor showing your commits with commands.

git rebase -i [commit-hash]

Or with a relative reference:

git rebase -i HEAD~3

This targets the last three commits.

Commands in the editor:

CommandEffect
pickKeep the commit as-is
rewordChange the message
editStop and modify the commit
squashCombine with previous commit
fixupCombine, discard this commit’s message
dropRemove the commit
execRun a shell command

Example — squash two commits into one:

pick abc1234 Add login form
squash def5678 Fix typo in login form

Git combines both into a single commit and lets you edit the message.

Reorder commits:

pick def5678 Fix typo in login form
pick abc1234 Add login form

Swapping the lines reorders the commits.

Why interactive rebase is powerful: It’s how you clean up a messy feature branch before merging. Squash, reword, reorder — shape the history as if you’d written it perfectly the first time.


Never rebase shared branches

If you’ve already pushed a branch to a shared remote, rebasing causes problems for others:

  1. It rewrites commit history — the commits others have are replaced by new ones
  2. Others’ local copies go out of sync
  3. It can cause conflicts or loss of work

The commits your teammates based work on no longer exist on the remote. Their local references point to ghosts.

The rule: Rebase only branches that are private to you. Once a branch is shared, use merge.

Why it’s dangerous: Git tracks commits by hash. If you rewrite the hashes, everyone else’s history diverges. They’d have to force-reset their branches, potentially losing work.


Rebase with remote branches

If you must rebase a shared branch, use --force-with-lease instead of --force:

git push --force-with-lease

Why --force-with-lease is safer: It only pushes if the remote is exactly where you last saw it. If someone else pushed in the meantime, the push fails — instead of overwriting their work.

Only do this when you’re certain no one else is working on the branch.

Never use plain --force on a shared branch unless you’re absolutely sure. It overwrites the remote unconditionally.

Why the lease: --force says “make the remote match my local state, no matter what.” --force-with-lease says “make the remote match my local state, but only if nobody else has pushed since I last fetched.” The second is safer.


Rebase vs merge at a glance

┌──────────────────────────────────────────────┐
│  Merge                                       │
│                                              │
│  main:    A ──► B ──► C ──► M                │
│                    \       /                 │
│                     D ──► E                  │
│                                              │
│  Preserves branch structure                  │
│  Creates a merge commit (M)                  │
│  Safe on shared branches                     │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│  Rebase                                      │
│                                              │
│  main:    A ──► B ──► C                      │
│                        \                     │
│                         D' ──► E'            │
│                                              │
│  Linear history                              │
│  No merge commit                             │
│  Rewrites commits — unsafe on shared         │
│                                              │
└──────────────────────────────────────────────┘

Complete Example Session

# ============================================
# PART 1: SETUP
# ============================================

git checkout main
git pull origin main

# ============================================
# PART 2: FEATURE BRANCH
# ============================================

git checkout -b feature/new-login

# Edit file1.js
git add file1.js
git commit -m "Add login form validation"

# Edit file2.js
git add file2.js
git commit -m "Implement password strength check"

# ============================================
# PART 3: MAIN MOVES FORWARD
# ============================================

git checkout main
git pull origin main
# [ Updating 1a2b3c4..5d6e7f8 ]
# [ Fast-forward ]

git checkout feature/new-login
# [ Switched to branch 'feature/new-login' ]

# ============================================
# PART 4: REBASE
# ============================================

git rebase main
# [ Successfully rebased and updated refs/heads/feature/new-login. ]

git log --oneline
# [ h8i9j0k (HEAD -> feature/new-login) Implement password strength check ]
# [ g7h8i9j Add login form validation ]
# [ f6g7h8i (main) Latest main commit ]
# [ ... ]

# ============================================
# PART 5: MERGE (FAST-FORWARD)
# ============================================

git checkout main
git merge feature/new-login
# [ Updating f6g7h8i..h8i9j0k ]
# [ Fast-forward ]
# [  file1.js | ... ]
# [  file2.js | ... ]

# ============================================
# PART 6: INTERACTIVE REBASE
# ============================================

git checkout -b feature/cleanup

# ... make 3 messy commits ...

git rebase -i HEAD~3
# [ Editor opens ]
# [ pick abc1234 Add login form ]
# [ squash def5678 Fix typo ]
# [ squash ghi9012 Fix typo again ]
# [ Save and close ]

# Git combines all three into one commit
# You edit the final message

# ============================================
# PART 7: FORCE-WITH-LEASE
# ============================================

git push --force-with-lease
# [ + abc1234...def5678 feature/cleanup -> feature/cleanup (forced update) ]

Quick Reference

Basic Rebase

CommandPurpose
git rebase mainRebase current branch onto main
git rebase -i COMMITInteractive rebase
git rebase --continueContinue after resolving conflict
git rebase --abortAbort the rebase
git rebase --skipSkip the current commit

Interactive Commands

CommandEffect
pickKeep as-is
rewordEdit message
editModify commit
squashCombine with previous
fixupCombine, discard message
dropRemove
execRun shell command

Rebase vs Merge

Use rebaseUse merge
Private branchShared branch
Before mergingAfter diverging
Clean historyAccurate history
Single authorMultiple authors

Safety

CommandSafe?
git rebase main✅ on private branches
git push --force❌ dangerous
git push --force-with-lease✅ safer
git rebase on shared❌ avoid

Rebase Conflicts

CommandPurpose
git statusSee conflicts
EditResolve markers
git add FILEStage resolution
git rebase --continueResume
git rebase --abortCancel

Best Practices

✅ Do This:

# Rebase your feature branch before merging
git checkout feature/login
git rebase main                              # ✅

# Use interactive rebase to clean up
git rebase -i HEAD~5                         # ✅

# Resolve conflicts and continue
git add file.js
git rebase --continue                        # ✅

# Use --force-with-lease when pushing
git push --force-with-lease                  # ✅

# Abort if things go wrong
git rebase --abort                           # ✅

# Rebase only private branches
# Never rebase shared ones                    # ✅

❌ Don’t Do This:

# Don't rebase a shared branch
git rebase main                              # ❌ on shared
git push --force                             # ❌ overwrites others

# Don't force-push without a lease
git push --force                             # ❌

# Don't rebase after merging to main
# It's already public                         # ❌

# Don't skip testing after rebase
# The code changed                             # ⚠️

# Don't rebase to avoid merge conflicts
# Resolve them instead                         # ⚠️

# Don't rebase a branch that's been pulled
# Others depend on those hashes                # ❌

Common Pitfalls

PitfallProblemSolution
Rebasing shared branchOthers’ history breaksMerge instead
Force-pushing without leaseOverwrites others--force-with-lease
Conflicts during rebaseRebase pausesResolve, --continue
Forgetting to push after rebaseRemote out of datePush with lease
Rebasing a merged branchNothing to rebaseNo-op or abort
Losing commitsWrong rangeVerify with git log
Rebasing without testingSilent breakageTest after

Real-World Examples

1. Rebase onto main

git checkout feature/login
git rebase main

Updates the feature branch to the latest main.

2. Interactive rebase

git rebase -i HEAD~3

Opens the last 3 commits for editing.

3. Squash commits

pick abc1234 Add feature
squash def5678 Fix
squash ghi9012 Fix again

Three commits become one.

4. Reorder commits

pick def5678 Fix
pick abc1234 Add feature

Swapping lines reorders them.

5. Reword a commit

reword abc1234 Add feature

Opens the editor to change the message.

6. Continue after conflict

git add file.js
git rebase --continue

Resumes the rebase.

7. Abort rebase

git rebase --abort

Returns to the pre-rebase state.

8. Skip a commit

git rebase --skip

Drops the current commit from the rebase.

9. Force-with-lease

git push --force-with-lease

Pushes safely after a rebase.

10. Verify after rebase

git log --oneline

Confirm the new history looks right.

11. Update feature from main

git fetch origin
git rebase origin/main

Keeps the feature current.

12. Check rebase state

git status

Shows whether a rebase is in progress.

13. Rebase onto a remote

git rebase origin/main

Rebases onto the remote’s main directly.

14. Preserve merge commits

git rebase --rebase-merges main

Keeps merge structure during rebase.

15. Rebase a single commit

git rebase --onto main HEAD~1

Reapplies only the last commit.


Visual: Rebase Replays Commits

┌──────────────────────────────────────────────┐
│  Before rebase                               │
│                                              │
│  main:    A ──► B ──► C ──► D                │
│                  \                           │
│  feature:         E ──► F ──► G              │
│                                              │
├──────────────────────────────────────────────┤
│  Git computes the diff for E, F, G           │
│  Rewinds to D                                │
│  Reapplies each diff                         │
│                                              │
├──────────────────────────────────────────────┤
│  After rebase                                │
│                                              │
│  main:    A ──► B ──► C ──► D                │
│                              \               │
│  feature:                     E' ──► F' ──► G'│
│                                              │
│  New commits with new hashes                 │
│                                              │
└──────────────────────────────────────────────┘

Visual: Rebase vs Merge History

┌──────────────────────────────────────────────┐
│  Merge                                       │
│                                              │
│  A ──► B ──► C ──► M                         │
│         \       /                            │
│          D ──► E                             │
│                                              │
│  Preserves branch structure                  │
│  Merge commit (M)                            │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│  Rebase                                      │
│                                              │
│  A ──► B ──► C ──► D' ──► E'                 │
│                                              │
│  Linear — no branches visible                │
│  No merge commit                             │
│                                              │
└──────────────────────────────────────────────┘

Visual: Interactive Rebase

┌──────────────────────────────────────────────┐
│  git rebase -i HEAD~3                        │
│                                              │
│  Editor opens:                               │
│                                              │
│  pick abc1234 Add login form                 │
│  squash def5678 Fix typo                     │
│  squash ghi9012 Fix again                    │
│                                              │
│  Save and close                              │
│                                              │
├──────────────────────────────────────────────┤
│  Result                                      │
│                                              │
│  h8i9j0k (HEAD) Add login form (combined)    │
│                                              │
│  One commit instead of three                 │
│                                              │
└──────────────────────────────────────────────┘

Visual: Safe Force Push

┌──────────────────────────────────────────────┐
│  git push --force                            │
│                                              │
│  Overwrites the remote                       │
│  No check for others' work                   │
│  ⚠️  Dangerous                               │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│  git push --force-with-lease                 │
│                                              │
│  Checks that remote matches your last fetch  │
│  Refuses if someone pushed                   │
│  ✅ Safer                                    │
│                                              │
└──────────────────────────────────────────────┘

Visual: When to Rebase

┌──────────────────────────────────────────────┐
│  Rebase when:                                │
│                                              │
│  ✅ Branch is private                        │
│  ✅ You haven't pushed yet                   │
│  ✅ Single author                            │
│  ✅ Preparing to merge                       │
│  ✅ Cleaning up history                      │
│                                              │
├──────────────────────────────────────────────┤
│  Merge when:                                 │
│                                              │
│  ✅ Branch is shared                         │
│  ✅ Others have pulled it                    │
│  ✅ Multiple authors                         │
│  ✅ Preserving merge history                 │
│                                              │
└──────────────────────────────────────────────┘

Summary

ConceptMeaning
RebaseReplay commits onto a new base
MergeCombine branches with a merge commit
Interactive rebaseEdit, squash, reorder commits
--force-with-leaseSafe force push
--continueResume after conflict
--abortCancel rebase
--skipDrop a commit
Rewrites historyNew hashes, new commits

Key takeaways:

  • Rebase replays your commits onto a new base — a linear alternative to merge
  • It rewrites history — new commits, new hashes
  • Rebase for private branches, merge for shared ones — that’s the rule
  • git rebase main updates a feature branch with the latest main
  • git rebase -i HEAD~N opens interactive mode — squash, reword, reorder, drop
  • Never rebase a branch others are working on — it breaks their local copies
  • If you must push a rebased branch, use --force-with-lease — never plain --force
  • Resolve conflicts during rebase and --continue; --abort to cancel
  • Rebase gives cleaner history; merge gives more accurate history
  • Use rebase before merging to keep main clean
  • Always test after rebasing — the code changed

Remember: Rebase is about history — rewriting it to look cleaner. Merge is about records — preserving how things actually happened. Use rebase on your own branches to keep history tidy. Use merge on shared branches to avoid disrupting others. And if you ever push a rebased branch, use --force-with-lease, never plain --force.

Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!