
September 11, 2026
11 min read
By Kokil Thapa | Last reviewed: September 2026
Interactive Rebase: Clean Up Your History is the Git workflow you reach for when a feature branch reads like a diary instead of a changelog. You fixed a typo, rebased twice, and left three "WIP" commits before the real work landed. Reviewers hate that noise. CI logs get harder to bisect. A clean, linear story makes rollbacks and code review faster. If you already understand when to rebase versus merge, interactive rebase is the next skill that turns messy branches into merge-ready history.
git rebase -i to edit, squash, reorder, or drop commits on your current branch before pushing. Run it on local or private feature branches, rewrite the todo list, then force-push with lease only after the branch is yours.What is interactive rebase and why should you clean up Git history?
Standard rebase replays commits onto a new base. Interactive rebase adds a pause step. Git opens an editor with a todo list. You choose what happens to each commit before Git applies the changes.
On production Laravel projects I maintain with ongoing support and maintenance, clean history is not vanity. It is operational hygiene. When a Deployer release breaks checkout on a sister site, I want one commit per logical change—not six fragments that say "fix fix fix".
Interactive rebase helps you:
- Combine small fix commits into one readable unit
- Split an oversized commit that mixed refactor and feature work
- Reword vague messages like "updates" into something searchable
- Drop accidental commits (debug dumps, wrong env files)
- Reorder commits so tests pass at every step in the replay
The trade-off is real. Rewriting history changes commit SHAs. Anyone who pulled the old branch must reset. That is why interactive rebase belongs on feature branches you own—not on shared main without team agreement.
How do you start an interactive rebase to clean up your Git history?
Pick your base carefully. Most feature work rebases against the integration branch tip or a fixed number of commits back.
Rebase the last N commits on your current branch
If your branch has five commits you want to tidy, run:
git checkout feature/booking-refactor
git fetch origin
git rebase -i HEAD~5 Git opens your configured editor with a file like .git/rebase-merge/git-rebase-todo. Each line is one commit, oldest at the top.
Rebase everything since you branched from main
This is the pattern I use before opening a merge request on GitLab CI pipelines:
git fetch origin
git rebase -i origin/main That includes every commit on your branch that is not on origin/main. It is ideal when you branched days ago and accumulated noise. For a deeper comparison of integration styles, see git rebase vs merge when to use each.
Step-by-step workflow
- Ensure your working tree is clean:
git statusmust show nothing unstaged. - Fetch remote refs so your base is current.
- Run
git rebase -iwith the correct range. - Edit the todo list (next section covers commands).
- Save and close the editor; Git replays commits one by one.
- Resolve conflicts if Git stops; use
git rebase --continue. - Run tests locally before any push.
- Push with lease:
git push --force-with-lease origin feature/booking-refactor.
On a Laravel booking app like Adventure Third Pole Trek, I often squash "fix migration" and "fix seeder" into the feature commit that introduced the schema change. Reviewers see one diff block. QA sees one revert target if something fails in staging.
What do pick, squash, fixup, and other interactive rebase commands do?
The todo file is the control panel. Git documents every action in the official git-rebase manual. These are the commands you will use daily.
| Command | What it does | When to use it |
|---|---|---|
pick | Keeps the commit as-is | Commits that already tell a clear story |
reword | Keeps changes but opens editor for message | Fixing "update" or ticket-only messages |
squash | Merges into previous commit; combines messages | Grouping related small commits with context |
fixup | Merges into previous commit; discards message | Typos, lint fixes, debug removal |
drop | Removes the commit entirely | Accidental or empty commits |
edit | Pauses rebase so you can amend the commit | Splitting one commit into two logical units |
Example: squash three fix commits into one feature commit
Your todo might start like this:
pick 8a1f2b3 Add Livewire booking step component
pick 3c4d5e6 Fix validation rule typo
pick 7g8h9i0 Fix CSS spacing on mobile
pick 1j2k3l4 Fix forgotten translation string Change it to:
pick 8a1f2b3 Add Livewire booking step component
fixup 3c4d5e6 Fix validation rule typo
fixup 7g8h9i0 Fix CSS spacing on mobile
fixup 1j2k3l4 Fix forgotten translation string Result: one commit with all four diffs. The final message stays as the first line unless you used squash and edited the combined message in the follow-up editor.
Example: reword a vague commit message
reword 9x8y7z6 Update stuff
pick 5a4b3c2 Add policy for booking cancellation Git stops after the first replay and opens your editor. Replace "Update stuff" with something like Restrict booking edits to confirmed reservations. Good messages match what you would want in a Laravel best practices changelog.
Example: split an oversized commit with edit
Mark the bloated commit with edit. When Git pauses:
git reset HEAD~1
git add app/Http/Requests/BookingRequest.php
git commit -m "Add Form Request validation for bookings"
git add resources/views/livewire/booking-step.blade.php
git commit -m "Add Livewire view for booking step"
git rebase --continue This pattern mirrors clean architecture separation—validation layer first, UI second.
A practical tip: prefer fixup over squash when the follow-up message adds no value. You avoid an extra editor step. If you need to craft a single polished message from three fragments, use squash and edit the result.
How do you safely interactive rebase without breaking shared branches?
Rewriting published history is the main risk. Force-pushing over someone else's work causes duplicate commits and painful merges. Follow these rules on every team I deploy for via Linux system administration and GitLab CI.
Golden rules
- Never interactive rebase
main,master, orproductionwithout explicit team policy. - Only rebase branches you alone are working on—or coordinate in chat first.
- Always use
--force-with-lease, never bare--force. - Rebase before review when possible; after approval, prefer a merge unless policy requires squash.
- Announce in the MR if you rewrote history after someone else fetched your branch.
--force-with-lease refuses to push if the remote moved since your last fetch. That blocks overwriting a teammate's push by accident.
git push --force-with-lease origin feature/notary-document-upload On legal-tech portals such as Notary Kathmandu, document-upload features often touch sensitive paths. A clean branch makes audit trails in merge requests easier for non-developer stakeholders.
When teammates already pulled your branch
They must realign after your force-push:
git fetch origin
git checkout feature/notary-document-upload
git reset --hard origin/feature/notary-document-upload Warn them first. Uncommitted local work on that branch will be lost with a hard reset.
Integrate with CI pipelines
After rebasing, your pipeline runs on new SHAs. That is expected. On sister sites sharing Deployer 7 + GitLab CI, I run PHPUnit and static checks locally before push. It saves a failed pipeline slot. Pair this habit with AI code review in CI only after history is stable—review tools diff against the target branch, and churn mid-review wastes everyone time.
How do you recover when an interactive rebase goes wrong?
Git keeps a reflog. Almost every failed rebase is reversible if you act before garbage collection prunes old entries.
Abort mid-rebase
git rebase --abort That returns your branch to the state before you started. Use this when conflicts spiral or you picked the wrong base.
Restore the branch to a known good commit
git reflog
git reset --hard HEAD@{3} Find the entry from before rebase -i started. The Pro Git book chapter on rewriting history explains reflog semantics in depth.
Rescue work after a bad force-push
If you force-pushed the wrong history, a teammate—or you on another machine—may still have the old commits locally. Find the old tip SHA in their reflog or yours, then:
git branch recovered/feature 8a1f2b3
git push origin recovered/feature On client portal projects, I document the pre-rebase SHA in the merge request description when the diff is large. Recovery takes minutes instead of hours.
Common failure modes
- Empty commit after squash: Git may skip empty commits; use
git commit --allow-emptyonly when you truly need a marker. - Conflict during replay: Fix files,
git add, thengit rebase --continue—same as a normal rebase. - Wrong commit order: Abort and reorder the todo list; tests should pass at each replay step.
- Accidentally dropped a commit: Recover SHA from
git reflogandgit cherry-pickit back.
When debugging messy diffs, a JSON formatter or regex tester helps validate API fixture changes—but fix the Git story first so you know which commit introduced the bug. That pairs well with AI-assisted debugging workflows where bisect needs clean boundaries.
Key Takeaways
- Run
git rebase -i HEAD~Norgit rebase -i origin/mainon feature branches before opening or updating a merge request. - Use
fixupfor noise commits andsquashwhen you need one combined message. - Push with
git push --force-with-lease—never rewritemainor shared branches without coordination. - Keep one logical change per commit so CI bisect and rollbacks stay predictable.
- Use
git rebase --abortandgit reflogas your safety net when something goes sideways. - Align clean history with team policy: some orgs squash-on-merge and skip local interactive rebase entirely.
People Also Ask
What is the difference between squash and fixup in interactive rebase?
squash merges a commit into the previous one and opens an editor to combine commit messages. fixup merges the changes but discards the follow-up message entirely. Use fixup for typo and lint fixes; use squash when the combined message should explain the full change.
Can you interactive rebase after pushing to remote?
Yes, on a feature branch you own. Rewrite locally, then force-push with lease. Do not rebase if teammates have based work on your old commits unless you coordinate their reset. Never rebase shared integration branches without team rules that allow it.
How many commits should you squash before a merge request?
Aim for one commit per logical unit: feature, test addition, or docs update. Three to seven commits on a large feature is normal. Fifty micro-commits is a signal to squash. Some teams prefer one squash commit per MR via GitLab "Squash on merge"—interactive rebase still helps you validate the story locally first.
Does interactive rebase change file content or only history?
It rewrites history, not intent. The final tree should match what you would get from merging the messy branch—unless you dropped or edited commits. Always run your test suite after rebasing. Content bugs mean a bad drop or conflict resolution, not a rebase quirk.
Ship cleaner branches on your next Laravel or WordPress project
Interactive Rebase: Clean Up Your History is a daily skill for anyone shipping via GitLab CI, Deployer, or plain SSH deploys. It turns noisy feature work into review-ready commits without changing what you built. Start on a throwaway branch, practice squash and fixup, then adopt force-with-lease on real merge requests.
If your team needs help tightening Git workflow, CI pipelines, or custom software delivery on PHP 8.3+ and Laravel 12/13, see the portfolio for production examples or testing and optimization services. For workflow questions on an active retainer, contact us—or read more on about me and server provisioning with Ansible for the full delivery stack.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

