Rewriting the Past: A Guide to Git Interactive Rebase

Overview

Maintaining a clean commit history isn't just about vanity; it's about communication. When you open a pull request, your history tells a story to the reviewer.

is the ultimate tool for refining that story. It allows you to rewrite history by combining, renaming, or deleting commits before they ever touch the main branch. This process transforms a messy series of "work in progress" (WIP) snapshots into a logical progression of features and fixes.

Prerequisites

To get the most out of this tutorial, you should be comfortable with basic Git operations like git add, git commit, and git log. Familiarity with terminal-based text editors—specifically

—is helpful, as rebase opens an interactive todo list in your default shell editor. You should also understand the concept of a HEAD pointer and how branches diverge from a common ancestor.

Key Libraries & Tools

  • Git: The core distributed version control system used for all commands.
  • Vim: A terminal-based text editor often used as the default interface for rebase todo lists.
  • Z: A command-line tool mentioned by
    Rissa Jackson
    for quickly navigating between project directories.

Code Walkthrough: Cleaning Your History

The Interactive Command

To start a rebase, you need to point Git to the commit before the ones you want to edit. Using the tilde (~) notation is the most reliable method.

git rebase -i [commit-hash]~

This opens an interactive list. Each line starts with the word pick. To change the history, you replace pick with a specific command.

Dropping and Rewording

If you have a commit that should never have existed, like a test file you accidentally committed, use the drop command. For simple typos in a commit message, use reword.

# Interactive Rebase Todo List
reword a1b2c3d Fix typo in post model
drop e5f6g7h Delete me: temporary debugging
pick i9j0k1l Add actual feature logic

Squashing and Fixing Up

These are the workhorses of a clean history. Both squash and fixup meld a "child" commit into the "parent" commit above it. The difference lies in the message: squash prompts you to combine both messages, while fixup discards the child’s message entirely.

# Using fixup to hide a small cleanup
pick a1b2c3d Main feature work
fixup e5f6g7h Oops, forgot a semicolon

Syntax Notes

You don't need to use the full 40-character SHA-1 hash. Git usually understands the first seven characters. In the rebase editor, simply changing the command word at the start of the line (e.g., from pick to f for fixup) is sufficient to trigger the change upon saving and exiting.

Tips & Gotchas

Rebasing is a destructive action because it generates new commit hashes. Never rebase commits that have already been pushed to a shared branch where others are working; you will break their history. If you must push rebased code to your own feature branch, use git push --force-with-lease. This "Canadian Force" command ensures you don't accidentally overwrite someone else's work if they added commits to the branch while you were rebasing locally. If things get confusing, your emergency exit is git rebase --abort.

3 min read