If you've been using Git for a while, you've probably come across a history full of useless commits, "WIP" messages, quick tests, or even empty commits created just to trigger a hook or pipeline. At that moment, you look at the log and think: “Nobody can understand this”The good news is you're not alone: it happens to all of us, and that's what support is for. git pass.
The interesting thing is that, when used properly, git rebase allows you to "clean up" the commit historyMake it linear, noise-free, and much easier to review. You can delete test commits, merge several into one, reorder them, correct commit messages, and even remove changes from the remote via a forced push. Let's take a look, with concrete examples, at how to use rebase to transform your history from total chaos into something organized and readable.
What exactly is Git Rebase and why does it affect the history?
When we talk about overtaking, we're literally talking about change the starting point of a branchGit takes the commits from your branch and "reproduces" them one by one on another base (usually the branch main or an updated remote branch). It's as if you went back in time and started your work from a different commit, but kept the changes.
Imagine that your project has this story in the main branch: A - B - CYou create a branch of functionality and then make two commits: D-EMeanwhile, in main, someone adds a new commit FWithout re-basing, your branches would look something like this:
A---B---C---F (main)
A---B---C---D---E (feature)
If you do a classic merge of your feature branch into main, what you get is an extra merge commit, which often generates tangled history with several crossed lines. With rebase, on the other hand, Git takes your commits D and E and reapplies them to F:
A---B---C---F---D'---E' (feature reescrita)
Those D' and E' These aren't exactly the same commits as D and E: they're "copies" with new identifiers (hashes). That's why we say rebase. rewrites the historyThe result is a more linear log, without intermediate merge commits that only add noise.

Why you should have a clean commit history
An organized record is not just a matter of aesthetics. A clean log makes daily work much easier.You can see at a glance what was done, when, and why, without having to jump through empty merge commits or context-free "quick fix" messages.
When branches are merged by constantly merging from main, you end up with a bunch of commits like “Merge branch 'main' into feature-x” that They don't provide real information about code changesThis complicates tasks such as:
- Locate the commit where a bug was introduced using tools for comparing filesbecause the history is full of irrelevant merges.
- Review pull requestsbecause you have to jump between redundant commits to see what actually changed.
- Understanding the evolution of a featureespecially if it has been developed over many small test commits.
Git rebase is the ideal tool to "polish" all that noise before sharing the branch with the rest of the team. It's like putting your history through the washing machine.You keep the important changes, well grouped and with clear messages.
Key differences between git merge and git rebase
To fully understand what you're doing when you use rebase, it's worth comparing its behavior to that of git mergeBoth serve to integrate changes, but in different ways and with important implications for the record.
With goGit creates a new merge commit that has two parents: the tip of your branch and the tip of the branch you're merging with. The resulting history preserves exactly the original sequence of commitsBut it can end up full of parallel branches and merges.
With passInstead of creating a merge commit, Git takes your commits and applies them one by one to the new database. This generates new commits with new hasheseven if the code changes are the same.
In practice:
- Go It maintains the history exactly as it happened, with all its cross-links and merges.
- rebase It generates a linear and clean history, as if everything had happened in a straight line.
The choice is not "one is better than the other" but which situation is each one more suitable for?To combine already shared and closed branches, merging is usually safer. To keep local working branches organized or before opening a pull request, rebase shines.

Cases where rebase shines: updating and polishing branches
There are two scenarios in which most developers use rebase daily: keep your working branch up to date with main y clean up commits before sharing themLet's look at them in more detail.
Update your feature branch with the latest changes from main
You're working on a new feature in your branch, but meanwhile your colleagues are still making commits in mainIf you want to integrate your changes, one option is to:
git checkout tu-rama-feature
git merge main
This works, but it generates a merge commit every time you sync, which eventually causes problems. a history full of repetitive mergesHowever, if you do:
git checkout tu-rama-feature
git rebase main
Git will move your commits on top of the last state of main, as if you had started the branch after those changes. You will get a linear history, without intermediate merge commitsand the review will be clearer.
Clean your commits before opening a pull request
It's very common that, during the development of a feature, you end up with commits like "typo fix", "pipeline testing", "more login changes", etc. That's fine while you're working, but That's not the kind of history you want to show. when you open a PR.
Interactive rebase lets you reorganize and group those commits into something more logical. For example, instead of this:
- WIP: añadir login
- Más cambios login
- Corregir tests login
- Arreglar typo variable
You can end up with a single, well-described commit:
- Añadir funcionalidad completa de login de usuario con tests
That "polishing" of the history makes it much easier for reviewers to understand. what each commit contributes and simplifies troubleshooting in the future.
Interactive rebase to rewrite history
The basic version of rebase simply moves your commits to another base. But the crown jewel is the interactive overbase, which opens an editor with the list of recent commits so you can decide what to do with each one.
The typical way to start this is by specifying how many commits back you want to "touch" from your current HEAD. For example:
git rebase -i HEAD~6
This command tells Git: “take the last 6 commits from this branch and prepare an interactive rebase for me.” Git will open your default editor (Vim, Nano, VS Code, etc.) with something like:
pick 7ed9c6e update version
pick ecb7ef3 empty commit 1
pick a323615 empty commit 2
pick 2c3d41d empty commit 3
pick d53c00f empty commit 4
pick 22dcc79 empty commit 5
# Rebase 549dd76..22dcc79 onto 549dd76 (6 commands)
#
# Commands:
# p, pick = use commit
# r, reword = Use commit, but edit the message
# e, edit = Use commit and stop to modify it
# s, squash = merge in the previous commit
# f, fixup = like squash, but discarding the message
# x, exec = execute shell command
# b, break = stop rebase here
# d, drop = delete commit
#...
Notice one important detail: The order in this file is the reverse of what you see in the git logIn the file, the first listed commit (7ed9c6e) is the oldest of the six, and the last (22dcc79) is the most recent. This is key to avoiding confusion when you delete or reorder commits.
Remove test or empty commits from the local history
Suppose that, to test a Git hook, you have been making empty commits with the option –allow-empty. For example: uterine
git commit -m "commit with no changes" --allow-empty
This option allows you to create a commit even when there are no changes to the files, which is useful for testing, but It clutters the history with commits that have no real value.Imagine your log looks like this:
git log --pretty=oneline --abbrev-commit
And you get:
22dcc79 (HEAD -> main, origin/main, origin/HEAD) empty commit 5
d53c00f empty commit 4
2c3d41d empty commit 3
a323615 empty commit 2
ecb7ef3 empty commit 1
7ed9c6e update version
In this scenario, you want to keep only the useful "update version" commit and remove all the others. empty commit which were just tests. To do this, you run the interactive rebase on the last 6 commits:
git rebase -i HEAD~6
The editor opens with the 6 commits. Your goal is to remove the lines corresponding to the empty commits. That is, you leave only something like this:
pick 7ed9c6e update version
# Rebase 549dd76..22dcc79 onto 549dd76 (6 commands)
# … rest of comments …
As indicated in the help section of the file itself, If you delete a line, that commit will disappear from the history. In the new, rewritten version, when saving and closing the editor, Git will only reproduce the "update version" commit and discard the others.
Uploading the new story to Origin: using forced push
So far, all rebase changes have been made to your local copy of the repository. If you want that cleanup to also be reflected in the remote repository (for example, in origin/main), you will have to overwrite the remote history with the new one.
This is done with a forced pushA common way to indicate this is by using the "+" sign in front of the branch name when pushing:
git push origin +main
That plus sign tells Git that Ignore the history divergence and overwrite the remote branch with your rewritten version. If you simply tried to do:
git push origin main
Git would warn you that your local history is not a direct descendant of the remote one (because you have rewritten commits with rebase) and would reject the push to avoid data loss.
It is important to understand that, after this forced push, Deleted commits will no longer be accessible from origin/mainThey may still be in reflogs or local copies from other colleagues, but for practical purposes you have cleared the remote history.
Serious warning: rewriting shared branches is not a game
Rewriting history sounds great for organizing everything, but it has a key consequence: when creating new commits with new hashes, You throw off anyone who had already based their work on the old commits.That's why the famous golden rule exists:
Do not rebase branches that are already shared and actively used by others.
In practice, this means it is safe to use rebase on:
- Local feature branches that you haven't published yet or that only you use.
- Recent Commitments those you know no one else depends on yet.
And it's a pretty bad idea to do it about:
- main, develop or any “official” branch from the repository that serves as a basis for several people.
- Shared work branches where there is more than one developer making commits.
If you rewrite the history of a shared branch and then perform a forced push, other teammates will find that Its branches point to commits that no longer exist in the remoteThey will have to perform more advanced operations (such as rebasing on the new history or resetting) to fix the mess.
Force push: better with a seatbelt
In many cases, after a rebase you'll need to force a push. The classic option is:
git push --force origin tu-rama
However, this variant overwrites anything on the remote without asking. To avoid accidentally overwriting other people's work, Git offers a much better option: –force-with-lease.
When you do:
git push --force-with-lease origin tu-rama
Git first checks that The remote is still in the state you thought it was. When you made your last pull or fetch. If it detects that someone has pushed new commits to that branch since then, the push is rejected and not forced, thus preventing you from overwriting other people's changes.
The idea is to combine rebase and force push, always with a cool head: only in branches that you control and using, whenever possible, –force-with-lease as an additional layer of security.
What to do when a rebase goes wrong: reflog to the rescue
It's happened to all of us: you start an interactive rebase, accidentally delete or change something, resolve a conflict incorrectly, and suddenly It seems you've lost some of your jobBefore you panic, remember that Git has an ace up its sleeve: git reflog.
The reflog is a local record of all HEAD movements: new commits, resets, rebases, merges… Thanks to it, you can locate where your branch was before you messed it up and return to that point with a hard reset.
The typical flow would be:
git reflog
There you will see a list of entries with something like:
abc1234 HEAD@{0}: rebase terminado
def5678 HEAD@{1}: checkout: moving from main to main
...
You identify the commit you were at before starting the rebase (for example, def5678) and you return to him with:
git reset --hard def5678
Thus, You completely undo the overlay and you revert to the previous state. You can also abort an ongoing rebase (if you're in the middle of the process and haven't finished) simply with:
git rebase --abort
This command leaves your branch as it was just before starting the current rebase, perfect for when conflicts appear that you don't want or you see that it's not a good time to continue.
Git pull vs git pull –rebase: small differences, big impact
Another point that often raises doubts is the difference between git pull normal and git pull --rebaseBoth update your local branch from the remote, but they do so in different ways.
When you run:
git pull
Git takes two steps: git fetch to bring the changes from the remote and then a git merge to combine them with your current branch. This may create an additional merge commit if you have local commits above the remote, which again history with unnecessary merges.
However, if you run:
git pull --rebase
Git does the fetch and then Replicate your local commits over the updated version on the remote.The result is similar to if you had done git fetch followed by git rebase origin/mainand maintains a cleaner, more linear history.
That's why many teams configure Git to use rebase by default when making pulls. It's a simple way to avoid automatic merge commits that don't contribute much.
Deleting files or images from history: general idea
Sometimes the problem isn't an ugly commit, but a file that should never have reached the repository: a wrong image, incorrect credentials, huge files, etc. The temptation is to go to GitHub and look for the trash icon, but if it appears disabled and shows messages like "you must be on a branch", it's because GitHub doesn't allow you to delete the history like that..
The correct way usually involves using history rewriting from your local machine (with interactive rebase or tools like git filter repo) and then perform a forced push. In GitHub Desktop you can also manage branches and commits, but the operation of completely remove a file from all commits This involves rewriting the history in a similar way to what we are seeing here.
In short, if you upload the wrong image or a sensitive file and want it to "disappear" from history, simply deleting it in the last commit is not enough: The commits where it was introduced need to be rewritten. and then force the push. It's a delicate operation and should be done calmly and in coordination with your team.
Practical flow for practicing overtaking without breaking anything
The best way to become proficient with overtaking is to practice in a controlled environment, without fear of disrupting anyone else's work. A simple flow would be:
- Create a new branch from main with git checkout -b my-branch-tests.
- Make several small commits (these can be trivial changes or test files).
- Simulate main progressing by making new commits there (or asking someone else to do it).
- Go back to your test branch and run git rebase main to see how your commits are repositioned.
- try a git rebase -i HEAD~3 to combine, rename, or delete any of the latest commits.
With this exercise you will see how Change the history before and afterHow Git responds to conflicts and how you can revert to previous states using reflog if needed. The more practice you have locally, the more confident you'll be when applying these techniques to real project branches.
Mastering Git rebase allows you to transform a chaotic history, full of empty commits, unnecessary merges, and meaningless messages, into a clear and readable sequence of significant changes. By using interactive rebase, deleting test commits, grouping scattered work, updating branches linearly, and leveraging tools like reflog and forced pushes with `--force-with-lease`, you can keep your repository in a much healthier and more professional state, as long as you remember to apply these techniques only to branches under your control and notify the team before rewriting the shared history.