Skip to main content

Unusual git rebase usage

· 10 min read
Serhii Hrekov
software engineer, creator, artist, programmer, projects founder

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.

  1. Start an interactive rebase, targeting the commit you want to split.

    git rebase -i bf123ac^
  2. Change the pick command for the target commit to edit.

    edit bf123ac Add user profile page and refactor API calls
  3. 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.

  4. Use git add -p or git add with specific files to stage the first set of changes (e.g., the user profile page code) and commit them.

  5. Stage the remaining changes (e.g., the API refactor) and commit them as a second commit.

  6. 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 before git push.
  • Rebase often on feature branches. Make it a habit to rebase your feature branch onto main or develop 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.