Interactive Rebasing
Interactive rebasing is one of Git's most powerful, and potentially complex, features. It allows you to rewrite your commit history in very flexible ways. You can reorder commits, combine (squash) commits, edit commit messages, or even remove commits entirely. It's like having a fine-grained editor for your commit history.
Warning: Interactive rebasing, especially on shared branches, can be risky because it rewrites history. Never rebase commits that have already been pushed to a shared repository unless you fully understand the implications and have coordinated with your team. Rebasing is generally safer and more appropriate for cleaning up your local commits before sharing them.
Let's create a series of commits to work with so we can see interactive rebasing in action:
echo "First change" >> hello.txt
git commit -am "First change"
echo "Second change" >> hello.txt
git commit -am "Second change"
echo "Third change" >> hello.txt
git commit -am "Third change"
These commands create three new commits in quick succession, each adding a line to our "hello.txt" file. The -am
shorthand in git commit -am
combines staging all modified and deleted files (-a
) with committing (-m
). It's a convenient shortcut when you've already been tracking files and just want to commit changes to them.
Now, let's say we want to clean up these three commits. Maybe "First change" and "Second change" were actually part of the same logical change, and we want to combine them into a single commit. And maybe we want to improve the commit message for "Third change". Interactive rebasing is perfect for this.
Start an interactive rebase session using:
git rebase -i HEAD~3
Let's understand this command:
git rebase -i
: git rebase
is the command for rebasing. The -i
flag stands for "interactive", which tells Git we want to perform an interactive rebase.
HEAD~3
: This specifies the range of commits we want to rebase. HEAD
refers to the current commit. ~3
means "go back three commits from HEAD". So, HEAD~3
selects the last three commits. You could also specify a commit hash instead of HEAD~3
to start the rebase from a specific point in history.
When you run this command, Git will open your default text editor with a list of the last three commits. The editor interface is where you instruct Git how to modify your history. You'll see something like this (the commit hashes will be different):
1 pick 63c95db First change
2 pick 68e7909 Second change
3 pick 5371424 Third change
4
5 ## Rebase 3bf348d..5371424 onto 3bf348d (3 commands)
6 ## 7 ## Commands:
8 ## p, pick <commit> = use commit
9 ## r, reword <commit> = use commit, but edit the commit message
10 ## e, edit <commit> = use commit, but stop for amending
11 ## s, squash <commit> = use commit, but meld into previous commit
12 ## f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
13 ## commit's log message, unless -C is used, in which case
14 ## keep only this commit's message; -c is same as -C but
15 ## opens the editor
16 ## x, exec <command> = run command (the rest of the line) using shell
17 ## b, break = stop here (continue rebase later with 'git rebase --continue')
18 ## d, drop <commit> = remove commit
19 ## l, label <label> = label current HEAD with a name
20 ## t, reset <label> = reset HEAD to a label
21 ## m, merge [-C <commit> | -c <commit>] <label> [## <oneline>]
22 ## . create a merge commit using the original merge commit's
23 ## . message (or the oneline, if no original merge commit was
24 ## . specified); use -c <commit> to reword the commit message
25 ## 26 ## These lines can be re-ordered; they are executed from top to bottom.
27 ## 28 ## If you remove a line here THAT COMMIT WILL BE LOST.
29 ## 30 ## However, if you remove everything, the rebase will be aborted.
Understanding the Interactive Rebase Editor:
- Commit List: The top part of the file lists the commits that are part of the rebase operation. By default, they are listed in reverse chronological order (oldest at the top, newest at the bottom).
- Commands: Below the commit list, Git provides a list of available commands that you can use to modify each commit. The most common ones are:
pick
(or p
): Use the commit as is. This is the default.
reword
(or r
): Use the commit, but allow you to change the commit message.
edit
(or e
): Use the commit, but stop the rebase process at this commit so you can make further changes (e.g., amend files, add more changes).
squash
(or s
): Combine this commit into the previous commit in the list. The commit message of this commit will be appended to the message of the previous commit (you'll get to edit the combined message later).
fixup
(or f
): Similar to squash
, but discards the commit message of this commit and just uses the message of the previous commit.
drop
(or d
): Remove this commit entirely from the history.
Our Interactive Rebase Plan:
We want to:
- Squash "Second change" into "First change".
- Reword "Third change" to have a better message.
To achieve this, modify the editor file to look like this:
pick abc1234 First change
squash def5678 Second change
reword ghi9101 Third change
Do not change the commit hashes in the editor file.
Instructions for Editing in Vim (if that's your editor):
- Enter Insert Mode: Press the
i
key. This allows you to type and edit the file.
- Make Changes: Use your arrow keys to navigate to the lines and change "pick" to "squash" and "reword" as shown above.
- Exit Insert Mode: Press the
Esc
key.
- Save and Quit: Type
:wq
and press Enter
.
After you save and close the editor file, Git will start performing the rebase operations based on your instructions.
First, Squashing Commits:
Git will first process the "squash" command. It will combine the changes from "Second change" into "First change". Then, it will open your text editor again, this time to let you edit the combined commit message for the squashed commit. You'll see something like this in the editor:
## This is a combination of 2 commits.
## This is the 1st commit message:
First change
## This is the 2nd commit message:
Second change
## Please enter the commit message for your changes. Lines starting
## with '#' will be ignored, and an empty message aborts the commit.
## ## Date: Tue Oct 24 10:30:00 2023 +0000
## ## interactive rebase in progress; onto <base_commit_hash>
## ## Last commands done (2 commands done):
## pick abc1234 First change
## squash def5678 Second change
## Next command to do (1 remaining command):
## reword ghi9101 Third change
## You are currently rebasing branch 'master' on '<base_commit_hash>'.
This editor shows you the original commit messages of the commits being squashed. You can now edit this text to create a single, coherent commit message for the combined commit. For example, you could change it to:
Combined first and second changes: Initial setup of hello.txt
After editing the message, save and close the editor.
Next, Rewording a Commit:
Git will then move on to the "reword" command for "Third change". It will open the editor yet again, this time to let you edit the commit message for "Third change". You'll see the original message:
Third change
## Please enter the commit message for your changes. Lines starting
## with '#' will be ignored, and an empty message aborts the commit.
## ## Date: Tue Oct 24 10:35:00 2023 +0000
## ## interactive rebase in progress; onto <base_commit_hash>
## ## Last commands done (3 commands done):
## pick abc1234 First change
## squash def5678 Second change
## reword ghi9101 Third change
## Next command to do (0 remaining commands):
## (finish)
## You are currently rebasing branch 'master' on '<base_commit_hash>'.
Change the message to something more descriptive, like:
Improved third change: Added a more descriptive line to hello.txt
Save and close the editor.
Rebase Completion:
After processing all the commands in your interactive rebase instructions, Git will finish the rebase process. You'll usually see a message like "Successfully rebased and updated refs/heads/master."
Verifying the Rebased History:
Now, check your commit log again:
git log --oneline
You should see a history that looks something like this (commit hashes will be different):
<commit_hash_third_revised> Improved third change: Added a more descriptive line to hello.txt
<commit_hash_combined> Combined first and second changes: Initial setup of hello.txt
<commit_hash_cherry_pick> Add new feature
<commit_hash_revert> Revert "Add line to be reverted"
<commit_hash_original_reverted> Add line to be reverted
<commit_hash_initial_amended> Initial commit with important note
Notice that:
- You now have fewer commits than before (we squashed two commits into one).
- The commit message for what was "First change" and "Second change" is now combined and more descriptive.
- The commit message for what was "Third change" has been reworded.
- The commit hashes of the rebased commits have changed because we rewrote history.
Important Reminders about Interactive Rebasing:
- Local History Tool: Interactive rebase is primarily a tool for cleaning up your local commit history.
- Avoid Rebasing Shared Branches: Do not rebase branches that have been pushed to a shared repository unless you are absolutely certain of what you are doing and have coordinated with your team. Rewriting shared history can cause serious problems for collaborators.
git rebase --abort
: If you make a mistake during an interactive rebase, or if you get confused or encounter conflicts you can't resolve, you can always use git rebase --abort
to cancel the rebase operation and return your branch to the state it was in before you started the rebase. This is a safe way to back out of a rebase if things go wrong.