Git Rebase and Interactive Rebase: Rewriting History Safely

Master git rebase and interactive rebase — squashing, splitting, rewriting commits, and understanding when to rebase versus when to avoid it.

published: reading time: 20 min read author: Geek Workbench updated: March 31, 2026

Introduction

Git rebase is one of those commands that rewards understanding but punishes carelessness. It takes your commits and replays them on top of a different base, creating a linear history instead of the branching mess that merge produces.

Interactive rebase (git rebase -i) goes further — you get to edit, reorder, squash, split, and drop commits before they’re replayed. It’s how you turn a messy series of “WIP” and “fix typo” commits into something worth showing to colleagues.

The catch: never rebase commits that have left your machine. Once others have pulled your work, rewriting that history means everyone has to reconcile divergent copies. Local branches, fine. Shared branches, don’t.

When to Use / When Not to Use

When to Use Rebase

  • Keeping feature branches current — rebase onto main instead of merging main into your branch
  • Cleaning up commit history — squash WIP commits before opening a pull request
  • Reordering commits — arrange commits in logical order for easier review
  • Splitting large commits — break monolithic commits into focused, reviewable units
  • Fixing commit messages — correct typos or add context to past commits

When Not to Use Rebase

  • Shared/public branches — rewriting pushed history disrupts every collaborator
  • After pushing to main — never rebase the main branch
  • When history preservation matters — if you need an accurate audit trail, use merge
  • During active collaboration on the same branch — coordinate with your team first
  • Binary file changes — rebasing binary conflicts is painful and error-prone

Core Concepts

Rebase moves your commits to a new base. Instead of creating a merge commit, it replays each commit one by one on top of the target.


Before rebase:
main:     A ── B ── C
           \
feature:      D ── E

After git rebase main:
main:     A ── B ── C
                       \
feature:                  D' ── E'

Note that D’ and E’ are new commits with different SHAs. The original D and E still exist but are no longer referenced by the feature branch.


graph TD
    Start["Feature branch: D-E"] --> Pull["git fetch origin"]
    Pull --> Rebase["git rebase origin/main"]
    Rebase --> Replay["Replay each commit\nonto new base"]
    Replay --> Conflict{"Conflict?"}
    Conflict -->|Yes| Resolve["Resolve, git add,\ngit rebase --continue"]
    Conflict -->|No| Next{"More commits?"}
    Resolve --> Next
    Next -->|Yes| Replay
    Next -->|No| Done["Linear history:\nA-B-C-D'-E'"]

Architecture or Flow Diagram


flowchart TD
    A["Interactive Rebase:\ngit rebase -i HEAD~4"] --> B["Editor opens with todo list"]
    B --> C{"Choose action per commit"}
    C -->|pick| D["Keep commit as-is"]
    C -->|reword| E["Keep changes, edit message"]
    C -->|squash| F["Combine with previous commit"]
    C -->|fixup| G["Combine, discard message"]
    C -->|edit| H["Stop for manual edits"]
    C -->|drop| I["Remove commit entirely"]
    D --> J["Git replays commits\nin order"]
    E --> J
    F --> J
    G --> J
    H --> K["Make changes,\ngit commit --amend,\ngit rebase --continue"]
    K --> J
    I --> J
    J --> L{"Conflicts?"}
    L -->|Yes| M["Resolve, add, continue"]
    L -->|No| N["Clean linear history"]
    M --> J

Step-by-Step Guide / Deep Dive

Reference: Basic Rebase

Reference: Common Interactive Rebase Operations

Basic Rebase


# Rebase current branch onto main
git switch feature-x
git rebase main

# Rebase onto a specific commit
git rebase abc1234

# Rebase onto remote branch
git rebase origin/main

# Rebase with automatic stashing (saves uncommitted work)
git rebase --autostash main

Interactive Rebase


# Interactively edit the last 4 commits
git rebase -i HEAD~4

# Interactively edit all commits since branching from main
git rebase -i main

# Editor opens with a todo list like:
# pick abc1234 Add user model
# pick def5678 Fix validation bug
# pick ghi9012 WIP: add tests
# pick jkl3456 Fix typo

Interactive Rebase Commands

CommandShortcutEffect
pickpUse commit as-is
rewordrUse commit, but edit the message
editeStop for amending the commit
squashsCombine with previous commit, keep both messages
fixupfCombine with previous commit, discard this message
dropdRemove the commit entirely
breakbPause rebase (you decide what to do)
execxRun a shell command after this commit
labellLabel current HEAD with a name
resettReset HEAD to a label

Common Interactive Rebase Operations

Squashing Commits


# Before (in interactive rebase editor):
pick abc1234 Implement login
pick def5678 Fix typo
pick ghi9012 Add tests
pick jkl3456 Fix test

# After (squash fixups into the main commit):
pick abc1234 Implement login
fixup def5678 Fix typo
fixup ghi9012 Add tests
fixup jkl3456 Fix test

# Result: single commit "Implement login" with all changes combined

Splitting a Commit


# In interactive rebase, mark the commit for editing:
edit abc1234 Large monolithic commit

# When rebase stops:
git reset HEAD~1              # Unstage the commit, keep changes
git add -p                    # Interactively stage first part
git commit -m "First logical change"
git add -p                    # Stage second part
git commit -m "Second logical change"
git rebase --continue         # Continue the rebase

Rewriting Commit Messages


# Change the last commit message
git commit --amend -m "New, better message"

# Change older commit messages via interactive rebase
git rebase -i HEAD~5
# Change 'pick' to 'reword' for commits you want to edit

Production Failure Scenarios

ScenarioImpactMitigation
Rebasing pushed commitsTeam members have divergent historyNever rebase shared branches; use merge instead
Force push after rebaseOverwrites remote historyUse --force-with-lease instead of --force
Losing commits during rebaseWork appears to vanishUse git reflog to find and recover lost commits
Rebase conflict cascadeMultiple conflicts in sequenceConsider merge; or resolve carefully one at a time
Accidentally dropping commitsPermanent data lossReview the todo list carefully before saving

Recovery After Bad Rebase


# If rebase is in progress
git rebase --abort

# If rebase completed but result is wrong
git reflog
# Find the pre-rebase HEAD position
git reset --hard ORIG_HEAD

# If you already force-pushed
# Team members should reset to the correct remote
git fetch origin
git reset --hard origin/main

Trade-off Analysis

ApproachProsCons
RebaseClean linear history, easier bisectRewrites history, dangerous on shared branches
MergePreserves true history, safe for sharedCreates merge commits, harder to follow
SquashSingle clean commitLoses individual commit context
FixupClean history, no extra messagesHides the fact that fixes were needed
Edit commitsPerfect commit granularityTime-consuming, requires careful planning
Drop commitsRemoves mistakes permanentlyLoses work; use reflog to recover

Implementation Snippets


# Complete workflow: clean up feature branch before PR
git fetch origin
git rebase origin/main          # Get current with main
git rebase -i HEAD~8            # Clean up commit history
# squash fixups, reword messages, drop WIP commits
git push --force-with-lease     # Update remote safely

# Auto-squash all fixup commits
git rebase -i --autosquash HEAD~10

# Rebase with exec to run tests after each commit
git rebase -i -x "npm test" HEAD~5

# Safe force push (won't overwrite others' work)
git push --force-with-lease origin feature-x

# Rebase only unpushed commits
git rebase -i @{upstream}

Observability Checklist

  • Logs: Record rebase operations in CI logs for audit trails
  • Metrics: Track rebase frequency vs merge frequency per team
  • Alerts: Alert on force pushes to protected branches
  • Traces: Link rebased commits to original PR numbers
  • Dashboards: Display commit hygiene scores (squash rate, message quality)

Security & Compliance Considerations

  • Rewriting history can break audit trails — use merge in regulated environments
  • Force pushes should be blocked on protected branches via platform settings
  • Signed commits retain their signatures through rebase only if git rebase --signoff is used
  • Document rebase policies in team contributing guidelines
  • Never rebase branches containing security patches that have been audited at specific commits

Common Pitfalls / Anti-Patterns

  • Rebasing after pushing — the cardinal sin of Git; creates divergence for everyone
  • Squashing everything — losing all commit granularity makes bisecting impossible
  • Forgetting to rebase before PR — reviewers see stale code and outdated conflicts
  • Using --force instead of --force-with-lease — can overwrite teammates’ pushes
  • Dropping commits accidentally — always review the interactive rebase todo list
  • Rebasing merge commits — creates duplicated history; merge commits should stay merged

Quick Recap Checklist

  • Rebase moves commits to a new base, creating new commit SHAs
  • Use git rebase -i to squash, split, reorder, and edit commits
  • Never rebase commits that have been pushed to shared branches
  • Use --force-with-lease instead of --force when updating remote
  • Recover lost commits with git reflog
  • Abort a bad rebase with git rebase --abort
  • Use --autosquash to automatically combine fixup commits
  • Run tests after rebase to verify nothing broke

Production Failure: Rebasing Shared Branches

Scenario: A developer rebases the develop branch after it has been pushed and pulled by 5 team members. The rebase rewrites all commit SHAs. Each team member now has a divergent history. When they push, Git rejects their commits as “non-fast-forward.” When they force-push to fix it, they overwrite each other’s work.

Impact: Team-wide history divergence, lost commits, hours of manual recovery, broken CI pipelines, and eroded trust in Git workflows.

Mitigation:

  • Never rebase branches that others have pulled
  • Protect shared branches (main, develop) with force-push restrictions
  • Use merge (not rebase) for integrating shared branches
  • Communicate rebase plans if absolutely necessary — coordinate with all team members
  • Use git reflog to recover lost commits after accidental rebases

# Block force pushes on protected branches (GitHub)
gh api repos/{owner}/{repo}/branches/main/protection \
  --method PUT \
  --field enforce_admins=true \
  --field required_pull_request_reviews='{"required_approving_review_count":1}'

# Recover from accidental shared rebase
git reflog
git reset --hard ORIG_HEAD  # restore pre-rebase state
git fetch origin
git reset --hard origin/develop  # align with remote

Trade-offs: Rebase vs Merge

DimensionRebaseMerge
History cleanlinessLinear, easy to readNon-linear, shows true integration points
Collaboration costHigh — requires coordination on shared branchesLow — safe for any branch
Recovery difficultyHard — rewritten SHAs, reflog neededEasy — revert the merge commit
Bisect friendlinessExcellent — single pathGood — but merge commits add noise
Audit trailRewritten — original context lostPreserved — records when/what was integrated
Conflict resolutionPer commit — may resolve same conflict multiple timesOnce — all conflicts resolved together
Team size impactDegrades with team sizeScales well to large teams
Best use caseLocal feature branches before PRShared branches, release integration

Security/Compliance: Why Rebasing Public Branches Breaks Audit Trails

In regulated environments (finance, healthcare, government), commit history serves as an audit trail. Rebasing destroys this trail:

  • Original commit timestamps are preserved, but committer dates change to the rebase time
  • Commit SHAs change, breaking links to CI runs, code reviews, and deployment records
  • Signed commits may lose their signatures during rebase unless --signoff is used
  • Blame history is disrupted — git blame shows the rebase author instead of the original author
  • Compliance requirements (SOC 2, HIPAA, SOX) often mandate immutable change records

# Preserve signoff during rebase (but not GPG signatures)
git rebase --signoff main

# Verify commit signatures after rebase
git log --show-signature

# Check if any commits lost their signatures
git log --format="%H %G?" | grep -v "^.* [GU]"

Best practice: Use merge (not rebase) for any branch that feeds into production deployments in regulated environments. Document the merge strategy in your compliance policy.

Rebase Architecture: Commit History Linearization

Before Rebase


graph TD
    A1["A"] --> B1["B"]
    B1 --> C1["C"]
    A1 --> D1["D"]
    D1 --> E1["E"]
    C1 -. "main" .-> C1
    E1 -. "feature" .-> E1

    classDef commit color:#00fff9
    class A1,B1,C1,D1,E1 commit

After git rebase main


graph TD
    A2["A"] --> B2["B"]
    B2 --> C2["C"]
    C2 --> D2["D' (new SHA)"]
    D2 --> E2["E' (new SHA)"]
    C2 -. "main" .-> C2
    E2 -. "feature" .-> E2

    classDef commit color:#00fff9
    class A2,B2,C2,D2,E2 commit

Key insight: D’ and E’ are entirely new commits with different SHAs. The original D and E become unreachable from the feature branch (though recoverable via reflog). This is why rebasing shared branches is destructive — other developers’ copies of D and E no longer match.

Interview Questions

1. What is the difference between git merge and git rebase?

git merge creates a new merge commit that combines two branches, preserving the true history of when work happened. git rebase replays commits onto a new base, creating new commits with a linear history. Merge is non-destructive; rebase rewrites history.

2. What is the difference between squash and fixup in interactive rebase?

Both combine a commit with the previous one. squash keeps both commit messages and opens an editor to combine them. fixup discards the squashed commit's message entirely, keeping only the previous commit's message. Use fixup for typo fixes and squash when you want to preserve context.

3. Why should you never rebase commits that have been pushed to a shared branch?

Rebase creates new commit SHAs for every rebased commit. If others have based work on the original commits, their history diverges from yours. They must manually reconcile the difference, which can cause lost work, duplicated commits, and team confusion.

4. How do you split a single large commit into multiple smaller commits?

Use git rebase -i and mark the commit as edit. When the rebase stops, run git reset HEAD~1 to unstage the commit while keeping changes in the working directory. Then use git add -p to selectively stage hunks for the first commit, commit it, repeat for subsequent commits, then git rebase --continue.

5. What does git push --force-with-lease do and why is it safer than --force?

--force-with-lease only force-pushes if the remote branch is exactly where you expect it (matching your last fetch). If someone else pushed new commits since your fetch, it refuses to push, preventing you from accidentally overwriting their work. --force overwrites without checking.

6. What does git rebase --onto do and when would you use it?

--onto rebases a range of commits onto a different branch, without requiring the source branch to be based on the target. The syntax is git rebase --onto <newbase> <upstream> <branch>. Use cases include: moving a feature branch that was branched from an old release to a new one, extracting commits from one branch to apply elsewhere, and correcting a mistaken branch base.

7. How do you resolve conflicts during an interactive rebase?

When a conflict occurs during rebase, Git marks the conflicting files. Resolve each conflict manually, then run git add <resolved-file> to stage the resolution. Do not run git commit. After staging all conflicts, continue the rebase with git rebase --continue. To abandon the entire rebase and return to the original state, use git rebase --abort.

8. What is the difference between git rebase -i and git rebase --autosquash?

git rebase -i opens an editor for manual control over each commit (pick, squash, fixup, edit, drop, etc.). git rebase --autosquash automatically squashes commits prefixed with fixup! or squash! when combined with interactive rebase (git rebase -i --autosquash HEAD~n). --autosquash saves time by not requiring manual ordering of fixup commits in the todo list.

9. When would you choose git merge over git rebase in a team workflow?

Choose merge when: working on shared branches (main, develop), collaborating with multiple developers on the same branch, operating in regulated environments requiring audit trails, or when preserving the true history of integrations matters. Merge is safer and non-destructive but creates merge commits. Rebase is for local-only cleanup before sharing a feature branch.

10. How does git reflog help recover from a bad rebase?

git reflog records every HEAD movement. After a bad rebase, run git reflog to find the commit hash before the rebase (shown as HEAD@{n} before the rebase operation). Then reset to that commit with git reset --hard HEAD@{n} or git reset --hard <hash>. Reflog entries are typically kept for 90 days by default.

11. What is the difference between git rebase --abort, --continue, and --skip?

--abort cancels the rebase entirely and returns the branch to its pre-rebase state. --continue resumes the rebase after you have resolved conflicts and staged the changes. --skip abandons the current commit being applied and moves on to the next one — useful when a commit's changes are no longer needed or conflict resolution is impractical.

12. Can you rebase merge commits? What happens if you do?

Technically yes (git rebase -p or manually), but it creates duplicated history and is not recommended. When you rebase a merge commit, Git replays the merged commits individually rather than preserving the merge as a single unit. This results in duplicate commits in the target branch. Merge commits should remain merges — rebasing them defeats the purpose of the merge and breaks history integrity.

13. How does git cherry-pick differ from git rebase for applying commits?

Cherry-pick applies specific individual commits from one branch to another, creating new commits with new SHAs for each picked commit. Rebase replays a range of consecutive commits onto a new base, preserving their relative order and relationships. Cherry-pick is selective; rebase is wholesale. Both create new commits — neither should be used on shared history.

14. What does git rebase --signoff do, and when is it important?

--signoff adds a Signed-off-by trailer to each rebased commit, similar to git commit --signoff. This is important in environments requiring Developer Certificate of Origin (DCO) compliance, such as Linux kernel development. Note that --signoff does not preserve GPG signatures — rebasing inherently changes commit SHAs, which breaks GPG verification.

15. What is an "upstream" branch and how does it affect git rebase?

The upstream branch is the tracked reference branch (set via git branch --set-upstream-to or git config branch.<name>.remote). git rebase (without arguments) rebases the current branch against its upstream. The @{upstream} or @{u} shorthand refers to this branch. For example, git rebase -i @{upstream} rebases only unpushed commits.

16. How does interactive rebase handle commits that are identical or empty after modifications?

Git automatically marks empty commits (no net changes) as droped during rebase. If two commits produce identical diffs, the second one becomes empty and is dropped. Commits that become invalid (e.g., due to conflict resolution making their changes redundant) are also silently dropped unless you explicitly prevent it. Use --keep-empty to preserve empty commits intentionally.

17. What are the implications of rebasing in a CI/CD pipeline?

In CI/CD: rebasing creates new SHAs, so any pipeline triggered by commit SHA will run again for rebased commits. Force-pushing may trigger security alerts or require additional permissions. Pipelines relying on commit-based caching may need invalidation. Signed commits may break verification. Best practice: rebase locally before pushing, not after, to minimize pipeline noise and security concerns.

18. How do you use git rebase -x (exec) to validate each commit during rebase?

git rebase -x "npm test" runs the specified command after applying each commit during rebase. If the command fails (non-zero exit), the rebase stops at that commit so you can fix the issue. This is useful for ensuring each commit in a series is independently valid — for example, running tests, linters, or build steps. Example: git rebase -i -x "npm test" HEAD~5.

19. What is the difference between a "soft reset" and a "hard reset" in the context of rebase recovery?

A soft reset (git reset --soft) moves HEAD to the target commit but preserves changes in the index and working directory. A hard reset (git reset --hard) resets everything — HEAD, index, and working directory — to the target state. During rebase recovery, --hard is typically used with ORIG_HEAD or a reflog entry to completely restore the pre-rebase state.

20. How does git bisect work with rebased branches?

git bisect performs a binary search through commit history to find a buggy commit. After rebase, the rebased commits have new SHAs but still contain the same changes, so bisect can still find bugs — it will identify the new commit hashes rather than the originals. Ensure you have tested both pre-rebase and post-rebase states when using bisect on a branch that was recently rebased.

Further Reading

Conclusion

Interactive rebase is the difference between a history that happened and a history that tells a story. Used responsibly on local and feature branches only, it’s one of Git’s most powerful cleanup tools. Squash, reorder, and edit commits before sharing — your collaborators will thank you.

Category

Related Posts

Rebase vs Merge: When to Use Each in Git

Decision framework for choosing between git rebase and git merge. Understand trade-offs, team conventions, history implications, and production best practices.

#git #version-control #rebase

Centralized vs Distributed VCS: Architecture, Trade-offs, and When to Use Each

Compare centralized (SVN, CVS) vs distributed (Git, Mercurial) version control systems — their architectures, trade-offs, and when to use each approach.

#git #version-control #svn

Automated Changelog Generation: From Commit History to Release Notes

Build automated changelog pipelines from git commit history using conventional commits, conventional-changelog, and semantic-release. Learn parsing, templating, and production patterns.

#git #version-control #changelog