Rebase
About
Git rebase is a mechanism that reapplies commits on top of another base tip, allowing us to relocate changes in history as if they had been made at a different point in time.
It does not merge histories. Instead, it transplants the work we have done onto a new base — as if our changes were made starting from that point.
In other words, rebasing is a history-rewriting operation. It reconstructs our branch from a new point in the project’s timeline.
Git is a distributed version control system. Every commit has a pointer to its parent(s), forming a directed acyclic graph (DAG). When branches diverge and change, they can be brought together by:
Merging: Preserves both histories as-is, introducing a new “merge commit” that connects the timelines.
Rebasing: Discards the old base and replays changes from one branch onto a new one, making it look like they always began from there.
How Rebase Works Internally ?
Rebase is a sequence of three steps:
Identify the Fork Point Find the last common commit between the current branch and the target branch.
Extract our Commits Take all commits after the fork point in our branch and treat them as patches.
Replay on Top of New Base Apply each patch on top of the tip of the target branch in order.
This results in:
A new set of commits (with new hashes).
A linearized history with no merge commits.
This is fundamentally a history rewrite operation — not a content merge.
Never rebase a branch others have pulled from. Because rebasing changes commit hashes, it invalidates the original timeline. Everyone else’s branch becomes incompatible — they now refer to commits that “don’t exist.”
Force pushing a rebased branch can overwrite collaborators’ work unless coordinated properly.
Use rebase for private or feature branches where we control the timeline.
We are working in a Git repository with two branches:
main
branch: contains commitsA → B → C
feature
branch: was created from commitB
, and has commitsD → E → F
feature
has diverged because new commits (C
) have been added tomain
afterfeature
was created.
What Happens During a Rebase ?
The command:
tells Git:
"Take the commits from the
feature
branch that are not inmain
(i.e.,D
,E
,F
), and replay them on top of the latest commit inmain
(i.e.,C
)."
Step-by-Step Breakdown
Git finds the common ancestor of
main
andfeature
, which is commitB
.Git creates patches (internal changes) for each of the commits on
feature
afterB
: → patches forD
,E
, andF
.Git checks out
main
(commitC
), and applies those patches one by one on top of it.New commit hashes are generated for the rebased commits — Git doesn't reuse
D
,E
,F
; it creates new ones (let's sayD'
,E'
,F'
).
Resulting History
After rebasing:
feature
now appears as if it was developed on top of the latestmain
.History is linear — no merge commits.
Old commits
D
,E
,F
are no longer part of the branch history.
Equivalent Merge (for contrast)
Had we used:
We'd get:
M
is a merge commit with two parents:C
andF
We retain full context of both branches’ histories
History is non-linear, with more branching complexity
Practical Use Cases
1. Keeping a Feature Branch Up to Date Without Polluting History
Problem: We are working on a feature branch for several days or weeks. In the meantime, our main branch (usually main
or develop
) continues to evolve with other features or bug fixes from the team. We want to bring in those updates to avoid conflicts later, but we don’t want merge commits cluttering our history every time we sync.
Why Rebase Works Well:
Rebasing allows us to replay our work on top of the latest state of main
, as if our work had started from the current version of the codebase. This produces a clean, linear history.
Effect: Instead of introducing merge commits and branching noise, rebasing keeps our branch’s commit history aligned with the project’s ongoing state — as if we were always up-to-date.
2. Preparing Clean Commits Before a Pull Request or Merge Request
Problem: During development, we often make multiple small, messy commits (fix typo
, oops
, refactor
, add test
, etc.). When preparing our branch for review, we want the commit history to be meaningful, concise, and structured.
Why Rebase Works Well:
Using interactive rebase (git rebase -i
), we can rewrite our history before it is pushed or shared. This allows us to:
Combine several commits into one (
squash
)Edit commit messages to reflect real purpose
Drop irrelevant or temporary commits
Effect: A reviewer sees a clean sequence of commits, each with a clear purpose. It makes our work easier to review, audit, and understand. The history appears deliberate, not accidental.
We can change pick
to:
squash
: combine commitsedit
: modify commit contentsreword
: change commit messagedrop
: remove commit
After saving, Git will apply changes step-by-step.
3. Avoiding Merge Conflicts Later by Resolving Them Early
Problem: When multiple branches change the same code, merge conflicts are inevitable. If we wait until the end of development to merge, conflicts might be large and hard to resolve because of accumulated differences.
Why Rebase Works Well: Rebasing early and often helps we resolve conflicts in small increments. By frequently rebasing onto the latest base branch, we continuously align with our team’s changes.
Effect: We deal with smaller, more understandable conflicts along the way, instead of a large, stressful merge crisis at the end of the development cycle.
4. Linear History for Bisecting and Debugging
Problem: When bugs are found in production, using git bisect
helps locate the exact commit where the bug was introduced. This tool relies on a clean commit history.
Why Rebase Works Well: With a linear history, each commit only introduces a specific logical change. This makes it easy to test each step and pinpoint the problematic one.
Effect: Debugging becomes much faster. Each commit becomes a reliable checkpoint in the evolution of our project.
5. Integrating Upstream Changes in Forked Repositories
Problem: In open-source or team workflows, we may fork a repository and develop a feature over time. Meanwhile, the upstream repository continues to evolve. Eventually, we want to sync our work with upstream without introducing complex merge paths.
Why Rebase Works Well: Rebasing our fork or branch onto the updated upstream branch makes it look as if our changes were always made against the latest code — no unnecessary merge bubbles are introduced.
Effect: When we submit a pull request or merge request, our code appears naturally integrated into the project, and maintainers don't need to wade through unnecessary merge conflicts or unrelated commits.
6. Reordering Commits for Logical Clarity
Problem: Sometimes the order of our commits doesn't match the logical flow of the code. We might have added a test before the implementation, or fixed a bug before writing the code that introduced it.
Why Rebase Works Well: With interactive rebase, we can reorder commits to match a logical, cause-and-effect sequence. This improves not just history readability, but also consistency in the project's timeline.
Effect: The commit history becomes a story: it can be read from top to bottom, and each change builds upon the previous one. This is invaluable for onboarding, documentation, and future debugging.
In the editor, simply move lines up or down to reorder the commits. Git will replay them in that order.
7. Dropping or Fixing Broken or Temporary Commits
Problem: We committed something temporary (e.g., debug logs, experiment, forgotten print statements) and don’t want that in the final project history.
Why Rebase Works Well:
Interactive rebase allows we to remove these commits (drop
) or edit them in-place (edit
) to fix issues without creating additional commits.
Effect: The history appears intentional and clean. There are no embarrassing or confusing commits left behind that need to be explained later.
Then in editor:
Save and Git will remove the dropped commit.
Last updated
Was this helpful?