|$ curl https://forge-ai.dev/api/markdown?path=docs/git/rebasing
$cat docs/git-—-rebasing-&-interactive-rebase.md
updated Recently·14 min read·published

Git — Rebasing & Interactive Rebase

GitIntermediate
Introduction

Rebasing is one of Git's most powerful — and controversial — features. It allows you to rewrite commit history by moving, combining, or modifying commits. When used correctly, rebasing produces a clean, linear project history that is easier to review, bisect, and understand.

This guide covers everything from basic rebasing to interactive rebase workflows, the golden rule, and how to handle conflicts when they arise.

What is Rebasing?

Rebasing is the process of moving a branch to a new base commit. Instead of creating a merge commit that ties two branch histories together, rebasing replays each commit from your current branch onto the tip of another branch, one at a time.

The result is a linear history with no merge bubbles. Each commit in the rebased branch gets rewritten with new parent pointers and potentially new content if conflicts are resolved during the replay.

conceptual
Bash
1# Before rebase:
2# A---B---C feature
3# /
4# D---E---F---G main
5#
6# git rebase main (from feature branch):
7# A'--B'--C' feature
8# /
9# D---E---F---G main
10#
11# The feature commits are replayed on top of G
12# A', B', C' are NEW commits (different hashes)
git rebase Basics

The standard rebase command replays commits from your current branch onto the tip of another branch. Here are the most common invocations:

terminal
Bash
1# Rebase current branch onto another branch
2git rebase main
3
4# Rebase onto a specific commit (by hash or reference)
5git rebase <commit-hash>
6
7# Rebase but preserve merge commits (--preserve-merges, deprecated)
8# Use --rebase-merges instead
9git rebase --rebase-merges main
10
11# Abort a rebase that went wrong
12git rebase --abort
13
14# Skip the current commit during rebase
15git rebase --skip
16
17# Continue rebase after resolving conflicts
18git rebase --continue
19
20# Pull with rebase instead of merge (config)
21git pull --rebase
22# Or set it permanently:
23git config --global pull.rebase true

info

Set git config --global pull.rebase true to make git pull rebase by default instead of creating a merge commit. This keeps your local history linear when pulling remote changes.
Rebase vs Merge

Both rebase and merge integrate changes from one branch into another, but they produce different histories. Understanding when to use each is key to maintaining a clean project history.

AspectMergeRebase
History shapeNon-linear (branch bubbles)Linear (no forks)
Commit hashesPreserved (original commits stay)Rewritten (new hashes for replayed commits)
Merge commitCreates a merge commitNo merge commit
Conflict resolutionDone once in the merge commitDone per commit being replayed
SafetySafe for shared branchesDangerous on shared branches
Use caseIntegrating finished featuresUpdating feature branch with latest main
terminal
Bash
1# Merge approach:
2git checkout feature
3git merge main
4# Creates: D---E---F---G main
5# # A---B---C Merge commit
6
7# Rebase approach:
8git checkout feature
9git rebase main
10# Creates: D---E---F---G main
11# # A'--B'--C' feature (linear)

best practice

Use git merge to bring finished feature branches into main. Use git rebase to update your feature branch with the latest changes from main before merging. This gives you a clean merge while avoiding conflicts during the final merge.
Interactive Rebase

Interactive rebase (git rebase -i) opens an editor where you can modify commits before replaying them. This is the cornerstone of craft Git history. You can squash, fixup, reword, reorder, edit, and drop commits.

terminal
Bash
1# Start interactive rebase for the last N commits
2git rebase -i HEAD~5
3
4# Or rebase everything since branching off main
5git rebase -i main
6
7# This opens your editor with a todo list like:
8# pick a1b2c3d Add login form
9# pick e4f5g6h Add validation
10# pick i7j8k9l Fix typo in validation
11# pick m0n1o2p Add tests
12# pick q3r4s5t Update README

Each commit in the todo list can be prefixed with a command:

CommandShortEffect
pickpUse the commit as-is
rewordrEdit the commit message only
editeStop to amend the commit (content + message)
squashsCombine with previous commit, merge messages
fixupfCombine with previous commit, discard message
dropdRemove the commit entirely

Squash & Fixup

Squash combines a commit with its predecessor and lets you merge the commit messages. Fixup does the same but discards the squashed commit's message — perfect for cleaning up typos and review fixes.

terminal
Bash
1# Before: messy history with "fix" commits
2# pick a1b2c3d Add login form
3# pick e4f5g6h Fix password validation
4# pick i7j8k9l Fix styling on login page
5# pick m0n1o2p Add tests
6# pick q3r4s5t Fix test assertion
7
8# After squashing fixes into the relevant commits:
9# pick a1b2c3d Add login form
10# squash e4f5g6h Fix password validation ← merged into login form
11# pick i7j8k9l Fix styling on login page ← keep (different concern)
12# pick m0n1o2p Add tests
13# fixup q3r4s5t Fix test assertion ← merged, message discarded
🔥

pro tip

Use git commit --fixup=<commit> to create a fixup commit, then git rebase -i --autosquash to automatically arrange and squash it. This saves you from manually reordering commits in the todo list.

Reword & Reorder

Reword lets you fix typos or improve clarity in commit messages. Reorder changes the sequence of commits, which is useful for grouping related changes before creating a pull request.

terminal
Bash
1# Before reorder:
2# pick a1b2c3d Add login form
3# pick m0n1o2p Add tests
4# pick e4f5g6h Add validation
5# pick q3r4s5t Add test for validation
6
7# After reorder (logical grouping):
8# pick a1b2c3d Add login form
9# pick e4f5g6h Add validation ← moved up
10# pick m0n1o2p Add tests ← tests for login form
11# pick q3r4s5t Add test for validation ← tests for validation
The Golden Rule of Rebasing

warning

Never rebase commits that have been pushed to a shared branch. Once a commit is in a remote repository that other developers might have pulled, rebasing it creates duplicate commits and causes confusion for the entire team.

Rebasing rewrites commit hashes. If someone else has based work on the original commits, their history will diverge from yours, and they will encounter duplicate commits and merge conflicts when they pull.

The golden rule: only rebase commits that exist solely in your local repository. Once a branch is shared via push or pull request, use merge instead.

terminal
Bash
1# SAFE to rebase: local commits not yet pushed
2git commit -m "WIP: add feature"
3git rebase main
4git push
5
6# UNSAFE to rebase: commits already pushed
7git push -u origin feature
8# ... another developer pulls this branch ...
9git rebase main # DO NOT DO THIS — rewrites public history
10git push --force # Forces everyone to recover
11
12# Safe alternative: use merge instead
13git merge main
14git push
Rebasing Feature Branches

The most common rebase workflow is keeping a feature branch up to date with main. This avoids a merge commit and ensures your feature can be merged with a fast-forward merge.

terminal
Bash
1# Start a new feature
2git checkout -b feature/payments main
3
4# Make some commits
5git commit -m "Add payment model"
6git commit -m "Add payment controller"
7git commit -m "Add payment tests"
8
9# Meanwhile, main has advanced. Update your branch:
10git fetch origin
11git rebase origin/main
12
13# If conflicts occur, resolve them and continue
14git add resolved-file.js
15git rebase --continue
16
17# Push the rebased branch (force push required!)
18git push --force-with-lease origin feature/payments
19
20# Create PR — merge will be fast-forward
21
22# After PR is merged, delete the branch
23git branch -d feature/payments

warning

Always use git push --force-with-lease instead of git push --force. The --force-with-leaseflag checks that your remote-tracking branch matches the remote, preventing you from accidentally overwriting someone else's commits.
Conflicts During Rebase

Unlike merge conflicts (which are resolved once), conflicts during rebase must be resolved for each commit being replayed. Git pauses after each conflicting commit and asks you to resolve it before continuing.

terminal
Bash
1# During rebase, Git stops at the conflicting commit:
2git rebase main
3# Auto-merging file.js
4# CONFLICT (content): Merge conflict in file.js
5# error: could not apply a1b2c3d... Add payment model
6
7# Check what's going on
8git status
9# You are in rebase — you are currently rebasing branch 'feature'
10# (fix conflicts and then run "git rebase --continue")
11
12# Resolve conflicts in your editor, then:
13git add file.js
14git rebase --continue
15
16# Git applies the next commit. Repeat if needed.
17
18# To see which commit is currently being applied:
19git log -1
20
21# To skip the problematic commit entirely:
22git rebase --skip
23
24# To abort everything and return to original state:
25git rebase --abort

info

Use git rebase --abort any time you feel lost during a rebase. It returns your branch to exactly the state it was in before you started the rebase, with no permanent changes.
Autosquash & Fixup

Git provides a powerful workflow for fixing up commits without manually editing the interactive rebase todo list. The --fixup and --squash flags create specially-named commits that --autosquash can recognize and arrange automatically.

terminal
Bash
1# Make a commit
2git commit -m "Add user authentication"
3
4# Oops, forgot to include the JWT library. Create a fixup commit:
5git add src/auth/jwt.js
6git commit --fixup HEAD
7
8# Later, more fixes:
9git add src/auth/session.js
10git commit --fixup HEAD
11
12# Start interactive rebase with autosquash:
13git rebase -i --autosquash HEAD~4
14
15# Git automatically reorders and marks the fixup commits:
16# pick a1b2c3d Add user authentication
17# fixup d4e5f6g fixup! Add user authentication
18# pick g7h8i9j Add tests
19# fixup j0k1l2m fixup! Add tests
20
21# You can also create squash commits:
22git commit --squash HEAD
23# Works the same way but uses "squash" instead of "fixup"
🔥

pro tip

Create a Git alias to save keystrokes: git config --global alias.fixup "commit --fixup". Then just run git fixup HEAD to create fixup commits quickly.
Best Practices

Rebase Before PR, Merge Into Main

Rebase your feature branch onto the latest main before creating a pull request, then use a regular merge (or squash merge) to bring the feature into main. This gives you a linear, conflict-free history without rewriting public commits.

Squash Before Merging

Use interactive rebase to squash WIP commits, fixups, and trivial changes into logical, well-described commits before merging. Each commit should represent one complete, testable change — not a stream of consciousness.

Never Rebase Shared Branches

If a branch has been pushed to a remote repository and other developers may have pulled it, do not rebase it. Use merge instead. The golden rule protects your team from history chaos.

Use --force-with-lease

When pushing a rebased branch, always use git push --force-with-lease instead of --force. It ensures you don't accidentally overwrite commits that someone else pushed since your last fetch.

Configure pull.rebase

Set git config --global pull.rebase true to rebase local commits on top of remote changes when pulling. This keeps your local history linear and avoids unnecessary merge commits.

$Blueprint — Engineering Documentation·Section ID: GIT-REBASE·Revision: 1.0