Unusual git rebase usage
Rebasing in Git is a powerful and often misunderstood tool. While its primary use is to integrate changes from one branch onto another, there are several "unusual" yet highly effective ways to leverage git rebase
to clean up your commit history, fix mistakes, and collaborate more smoothly.
This guide explores some of these advanced use cases, focusing on their practical application and best practices.
Interactive Rebase for Cleaning Up Commits
The most common "unusual" usage of git rebase
is the interactive mode. This allows you to rewrite commit history on a branch before merging it into another. It's an indispensable tool for a clean, linear, and understandable project history.
You initiate an interactive rebase with the -i
flag, specifying a commit to rebase from. A common practice is to rebase against main
or develop
to clean up your feature branch before a pull request.
Let's say you're on a feature branch named my-feature
and want to clean up your last 5 commits. You'd run:
git rebase -i HEAD~5
This will open your default editor with a list of the last 5 commits and a set of commands you can use.
pick 623f71c Add user login form
pick 1b7c84a Fix typo in user model
pick d9e8f2e Add validations to login form
pick 5a4b3d1 Forgot to add a file, adding it now
pick f7f1b2c Tweak login button styling
# Rebase 623f71c..f7f1b2c onto 623f71c (5 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# d, drop <commit> = discard commit
# l, label <label> = label current HEAD with a name
# t, tag <tag> = add a tag to current HEAD
# m, merge [-C <commit> | -c <commit>] <label> [...]
# .
# .
# .
Squashing and Fixing Up Commits
The most useful commands here are squash
and fixup
. They allow you to combine multiple small, "work-in-progress" commits into a single, meaningful commit.
squash
: Combines a commit with the one before it and allows you to edit the new commit message. This is great for combining a series of related changes into a single logical unit.fixup
: Similar to squash, but it discards the commit message of the commit being merged. This is perfect for "typo fixes," "forgot to add a file," or other minor commits that shouldn't have their own message.
Example of squashing and fixing up:
To combine the "Fix typo" and "Add validations" commits with the "Add user login form" commit, and fixup
the "Forgot to add a file" commit, you'd edit the rebase file like this:
pick 623f71c Add user login form
squash 1b7c84a Fix typo in user model
squash d9e8f2e Add validations to login form
fixup 5a4b3d1 Forgot to add a file, adding it now
pick f7f1b2c Tweak login button styling
When you save and close the editor, Git will combine the commits as instructed, prompting you to write a new commit message for the squashed commits. This results in a cleaner history.
Splitting a Commit into Multiple Commits
Sometimes, you make a large commit that contains several unrelated changes. This is bad practice and makes it hard to review and debug. Interactive rebase can help you split this monolithic commit.
Let's say you have a single commit bf123ac
titled "Add user profile page and refactor API calls." This should be two separate commits.
-
Start an interactive rebase, targeting the commit you want to split.
git rebase -i bf123ac^
-
Change the
pick
command for the target commit toedit
.edit bf123ac Add user profile page and refactor API calls
-
Save and exit. Git will stop at this commit. Now, you can run
git reset HEAD^
to un-commit the changes, while keeping them in your working directory. -
Use
git add -p
orgit add
with specific files to stage the first set of changes (e.g., the user profile page code) and commit them. -
Stage the remaining changes (e.g., the API refactor) and commit them as a second commit.
-
Finally, run
git rebase --continue
to finish the rebase.
You've now successfully split one large, messy commit into two clean, logical ones.
Rebasing with --onto
The --onto
flag is less common but incredibly powerful for moving a series of commits from one base to another. Imagine you have a branch my-feature
based on an old main
, but you want to rebase it onto a different, newer branch named new-base
.
Here's a visual representation of this scenario:
A -- B -- C -- D (main)
\
E -- F -- G (my-feature)
Now, let's say a new branch new-base
was created from commit C
with some new commits on it.
A -- B -- C -- D (main)
\
H -- I (new-base)
You want to take commits E
, F
, and G
and reapply them on top of new-base
. You can achieve this with git rebase --onto
:
git rebase --onto new-base C my-feature
This command says: "Take all commits on my-feature
that are not on C
, and re-apply them on top of new-base
." The result is a new my-feature
branch with a history like this:
A -- B -- C -- D (main)
\
H -- I (new-base)
\
E -- F -- G (my-feature)
This is extremely useful when your team decides to switch the base branch for a feature, or when you need to "lift" a commit series from one part of the tree to another.
Best Practices and Cautions
- Never rebase a shared branch. The golden rule of rebasing is to never rebase a branch that has been pushed and is being used by other developers. Rebasing rewrites history, which means if others have pulled your old commits, their history will diverge from yours, leading to major merge conflicts and headaches. The only exception is when you know for sure no one else has pulled your branch yet (e.g., in a small, private team setting where you communicate this).
- Always pull before you push. If you are working on a shared branch that is not a feature branch, and you have to rebase it, always make sure you
git pull
beforegit push
. - Rebase often on feature branches. Make it a habit to rebase your feature branch onto
main
ordevelop
frequently. This keeps your branch up-to-date with the latest changes and helps you resolve conflicts in small, manageable chunks rather than a single, massive merge conflict at the end. - Understand the tool. Rebasing can be destructive if not used correctly. Always have a clear understanding of what you're doing.
git reflog
is your best friend; it allows you to find lost commits if you mess up a rebase.