Working with Git daily can turn your repository history into a real battleground if you're not careful. Half-hearted commits, noisy merges, and constant conflicts This makes understanding what has happened in the code increasingly complicated, especially as the project grows or new people join the team.
To avoid this chaos, there is a tool that many people know of by hearsay but don't quite master: git passWhen used correctly, it allows you to maintain a linear, readable, and easily reviewable history, without those merge commits that only take up visual space and without giant pull requests that nobody wants to review. Let's take a closer look at what it actually does, when it's appropriate to use it, what its dangers are, and how to leverage it in real-world workflows.
What is git rebase and why does it affect the history?
In a nutshell, git rebase is a command that rewrites the history of GitInstead of mixing branches as it does git mergeWhat it does is "move" or "reapply" your commits on top of another reference point, as if you had started working from there from the beginning.
When you perform a rebase, Git identifies from which commit two branches diverge and Recreate one by one the commits that only exist in your branch On top of the commit you specified as the base (another branch or a specific hash). The content and messages of the commits remain the same, but the identifiers (the hashes) change, because for practical purposes they are new commits.
This has an important consequence: Any operation that alters the history changes the commit hashesIf those commits have already been shared with others (push to remote, open pull request, etc.), rewriting them can cause your colleagues' repositories and the remote to become out of sync, forcing everyone to perform extra operations to recover a consistent state.
That is why it is often said that Rebase is used to "clean up the story" before making it publicIn local branches or commits that only you have touched, it's an incredibly powerful tool. In shared branches, however, you have to tread very carefully.
Basic use of git rebase for a clean history
Imagine you are developing a new feature in a branch feature-caracteristicaand over several days you make a series of commits: aplicaCambioA, aplicaCambioB, aplicaCambioD. Meanwhile in master (o main) someone fixes an urgent bug with a commit aplicaCambioC.
At this point, master and your feature branch have diverged. If you switch back to your branch and do a git merge masterGit will perform a recursive merge and create a additional merge commitThat merge commit doesn't add new functionality, it only reflects how two lines of work have been joined, and it often makes the commit tree harder to follow.
If instead you run it on your branch git rebase masterGit will take the commits that are only in feature-caracteristica y It will reproduce them right after the last commit of masterVisually, your history ceases to be a graph with intersecting branches and becomes a linear sequence where changes appear to have been made one after the other without bifurcations.
The great advantage of this approach is that, when you later merge back into masterGit can do a Fast-forward (advance the reference without creating a new merge commit). This keeps the history clean and easy to read, as many unnecessary merges disappear.
Now, not everything is perfectDuring a rebase, when recreating commits on a new base, conflicts can arise. These conflicts are resolved in the same way as in a classic merge, but the difference is that a merge commit is not generated; instead, the resolution is incorporated within the commit being reapplied.

Do not rearrange the public record
One of the key principles when using rebase is very simple: Don't rewrite the story you've already shared with other peopleIf you've pushed a branch to a remote and someone else has relied on those commits, rewriting that branch with a rebase forces everyone else to perform complicated operations (like git pull --rebase with care or even hard resets) to resynchronize.
The rule of thumb is usually: Use rebase freely on your machine before publishing the branchOnce a branch has been pushed to a remote repository that others use, it's best to avoid rewriting it. There are exceptions (for example, very disciplined teams that accept rebasing on shared branches following clear rules), but as a general recommendation, it's safer not to touch the public branch history.
When you're working entirely alone on a branch, or when the team uses pull requests and it's assumed that Each developer can reorder their own feature branch before it is reviewed.Rebase is a great ally for presenting a set of clean, consistent, and easy-to-understand commits for anyone reviewing the code.
Standard Git Rebase vs. Interactive Git Rebase
A "normal" rebase simply takes your commits and reapplies them one by one to another base. But Git includes a much more powerful mode: the interactive overbase, which allows you to decide what to do with each commit before reapplying it.
When you execute a command like this git rebase -i origin/developmentGit opens your default editor and shows you something like this:
pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
# Rebase 710f0f8..a5f4a0d onto 710f0f8
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
# Note that empty commits are commented out
In this listing, each line represents a commit that Git will reapply. You can change the commands at the beginning of each line or even the order of the lines to control exactly how the story will look.
Additional reorganization commands in interactive rebase
The main commands that appear in that file are:
pick(P): applies the commit as is, without changes.reword(r): reapplies the commit but allows you to modify the message.edit(E)Git stops after applying that commit so you can make extra changes (modify files, add more changes, etc.) before continuing with the rebase.squash(S): combines this commit with the previous one, unifying their changes and allowing you to edit the resulting message.fixup(f): same assquashbut it discards the message from the current commit and keeps only the message from the previous commit.exec(x): executes a shell command at that point in the rebase, useful for automating tests or other checks.
Furthermore, You can rearrange the lines To change the chronological order of commits, delete a line to completely remove a commit, or even leave the file empty to abort a rebase. However, if you delete a line, that commit disappears from the history, so it must be done intentionally.
This mechanism allows you to delete commits that no longer make sense (for example, a change that you later reverted), merge several small commits of the same functionality into a single, more meaningful one or polish commit messages that you initially wrote in a rush.
Practical summary of the use of rebase and interactive rebase
By combining standard and interactive rebase you can achieve a very comfortable flow: You work naturally, with frequent, even somewhat "dirty" commits.And when the functionality is mature, you do an interactive rebase to leave a flawless history.
For example, after a week on a branch feature/It's normal to have commits of failed attempts, quick fixes like "fix silly bug," or reverts of things you tried that didn't work. git rebase -i origin/development You can group several related commits using squash o fixup, delete commits that were invalidated and correct the order of the changes so that the history tells a clear story.
Once you save the rebase file and exit the editor, Git will begin applying the commits according to your instructions. If conflicts arise during the process, you'll need to resolve them just like with a merge and then execute the command. git add about the affected files and git rebase --continueWhen everything is finished, your log will show a neat and orderly sequence of commits, and you will typically need to do the following: git push --force or better git push --force-with-leasebecause the history of the branch has changed.
Configuration options that make rebase easier to use
To integrate rebase into your daily routine, Git offers some configuration options that automate behaviorsand even allows GitHubActions to automate parts of the workflow. For example, you can configure Git so that when you do git pull Use rebase instead of merge by default, avoiding "automatic" merge commits with each update.
It's also common to configure the editor that opens for interactive rebases or adjust aliases in your .gitconfig to have shortcuts like this grbi all with git rebase -iThis doesn't change how the overpass works, but it does make it more comfortable and reduces friction when using it frequently.
Another good practice is to get used to using --force-with-lease instead of --force When you push after a rebase, this option checks that the remote branch hasn't changed since you last viewed it. If it detects new changes, it alerts you instead of overwriting them, preventing you from inadvertently overwriting someone else's work.
Advanced reorganization app: squash, clean, and tidy
One of the most useful applications of interactive rebasing is commit unification, also called squashImagine you have three consecutive commits that are actually part of a single idea: you implement a feature, then fix a typo, and then tweak a style detail. For the record, it probably makes more sense to put all of that in one well-described commit.
During interactive rebase, you can mark the first commit as pick and the following as squash o fixup. Git will combine all of their changes into a single commit; with squash It will ask you to edit the resulting message and with fixup It will simply retain the message from the first one.
In addition to unifying, you can Reorder commits to make the history more logical.For example, you can move up a commit that introduces a data model, and then move up the commits that add controllers and endpoints that depend on it. You can also delete commits that have become obsolete (for example, you introduce something and then completely remove it in a later commit).
When you finish this process, you git log It becomes much more pleasant to read: less noise, less back and forth, more atomic and self-explanatory commitsAnd that's very noticeable when doing code reviews or investigating past bugs.
Understanding the dangers of reorganizing the record
Rewriting history is powerful, but also delicate. The first risk is the one already mentioned: If you rebase commits that other people have already downloaded, you're creating an artificial fork.Your history and theirs are separated, and Git doesn't know how to reconcile them without manual intervention.
Another common problem comes from accidentally deletes commits during an interactive rebaseIf you delete a line in the rebase list thinking "it's no big deal," that commit disappears from the new history. Sometimes that's what you want, but other times you can lose important changes without realizing it if you don't check carefully.
You also have to be careful with repetitive conflicts: when the rebase is long and there are many commits, you may find yourself resolving similar conflicts several times, because Git applies the changes one by one to the new databaseThis is the price of achieving a clean story, but it's worth being aware of it so as not to get bogged down in very heavy branches.
Finally, if you overuse rebasing on shared branches, you can make the team's workflow fragile. Each forced overtake forces the rest to understand what has happened, updating its branches, resolving added conflicts and, in general, spending time fighting with the tool instead of with the business problem.
Recovering from a complicated reorganization with reflog
Although it may seem intimidating, Git offers mechanisms for undo a rebase that went wrongThe key tool here is git reflogwhich keeps a record of all your pointer movements HEAD (branch changes, resets, rebases, merges, etc.).
If you perform a rebase and then realize you've rebased the wrong branch, lost commits you didn't intend to delete, or simply left the history in a strange state, you can run git reflog to see the list of previous statesEach line has a type identifier HEAD@{n} associated with a previous commit.
When you locate the point you want to return to, you can use git reset --hard HEAD@{n} (adjusting that) n) for restore the repository to its pre-rebase stateIt's a kind of internal "time machine" in Git that gives you peace of mind to experiment with rebasing without fear of irreversibly breaking everything.
In more everyday scenarios, if an overrun causes too many conflicts or leaves the branch in a state you're not happy with, you can also abort it with git rebase --abort, which tries to return you to the point where you were before starting it.
Using git rebase in real-world workflows: stacked branches and smaller pull requests
In addition to cleaning up the history of a single branch, rebasing is key in more advanced workflows, such as branch stackingThis approach helps avoid those monstrous pull requests, with dozens of changed files and thousands of new lines, that nobody wants to seriously review.
The idea is to structure your work into several hierarchical branches, each focused on a clear concept. For example, you could have something like: main → feature-database-setup → feature-api-endpoints → feature-authentication → feature-error-handlingEach branch is created from the previous one, not directly from the previous one. main.
In the forge (GitHub, GitLab, etc.), instead of opening all PRs against main, You define the previous branch of the chain as the base of each PR: the PR of feature-api-endpoints point to feature-database-setupThat of feature-authentication point to feature-api-endpointsand so on. In this way, each pull request only shows the truly new changes compared to the immediately preceding branch, making reviews much more specific and manageable.
When one of the base PRs is approved and integrated into mainRebase comes into play. You switch to the next branch of the stack, you execute git rebase main and then you update the remote with git push --force-with-leaseThe magic is in that The PR of that branch is now recalculated with respect to main and shows a clean diff only with the changes that are not yet in the main branch.
With this approach, review times drop dramatically: instead of having endless PRs that get stuck for days, you move to small PRs, each with a well-defined idea. Merge conflicts are also reducedbecause each branch is frequently re-based on the updated base, and the commit graph remains much more organized.
Best practices and tips for mastering git rebase
For all of this to work in practice, it's advisable to adopt some simple rules. The first is to define branches with a clear scope: a branch, a conceptIf you find it difficult to explain in one sentence what a branch contributes, you should probably divide it into two or more more specific branches.
It also helps a lot to use Branch names that tell a storyavoiding generic types feature-updates o feature-fixesSomething like this is better feature-user-auth-foundation, feature-payment-processing-core o feature-notification-system, which make it clear at a glance what each branch contributes to the workflow.
Regarding forced pushes, the golden rule is clear: always use --force-with-lease instead of --forceThis way you avoid overwriting remote changes you haven't seen, and if someone has pushed something new to the branch, Git will warn you before you mess things up.
Another key recommendation is to prefer rebase on merge to update working branches (for example, bring the latest changes from main o development (to your feature). This maintains a linear history and makes it much easier to track the project's evolution, especially when reviewing old code or investigating the source of a bug.
Final considerations
Finally, get used to doing small, well-described commitsEven if you later squash the changes in an interactive rebase, having granular commits gives you the flexibility to decide what to merge and what to keep separate. And if you ever need to bisect the code to find the source of a bug, you'll be glad that each commit represents a reasonably well-defined change.
When you integrate all these ideas into your daily workflow (rebase to keep the branch up to date, iterative rebase to polish before submitting a PR, stacked branches to break down large features, and conscious use of --force-with-lease y reflog to recover from mistakes), Git ceases to be an obstacle and becomes an allyYour history will be more readable, reviews will be faster and of higher quality, and merge conflicts will go from being a weekly nightmare to an occasional, controlled annoyance. Share the information and more users will know all about Git Rebase.