Rewrite Git History with Rebase

Rewrite Git History with Rebase

·

6 min read

I discussed why and how to use git rebase instead of git merge in one of my previous article. Actually, the git rebase command has a interactive mode, which can be used to rewrite git history.

Reorder commits

Suppose we have a git history like below. We want to reorder these commits.

% git log --oneline
ff02432 (HEAD -> master) 3
316ed3d 2
8ff22f6 1

Let's use the git rebase interactive mode, to see how to solve this problem.

First, let go into the interactive mode.

git rebase -i HEAD~2

This HEAD~2 part specify the target commits. The number 2 means latest 2 commits.

We can also use the the commit hash to specify the target commits. Below command means all the commits after the commit 8ff22f6.

% git rebase -i 8ff22f6

Then the default editor(vim for me) will be opened and a screen of blew will be showed.

pick 316ed3d 2
pick ff02432 3

# Rebase 8ff22f6..ff02432 onto 8ff22f6 (2 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 [-C | -c] <commit> = like "squash" but keep only the previous
#                    commit's log message, unless -C is used, in which case
#                    keep only this commit's message; -c is same as -C but
#                    opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified); use -c <commit> to reword the commit message
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#

As you can see, the first part shows all the target commits with the order as the oldest commit on the top. The second part is lines start with #, which means they are all comments. These comments show a list command can be used to make changes to commits. For example, the top one and also the default one is pick, which means use current commit as it is.

To reorder commits, we don't really need to use the command, we can just change the order of commit hash. So we change them as below.

pick ff02432 3
pick 316ed3d 2

Then save the changes and close the vim editor, git should tell us the rebase process succeeded.

% git rebase -i 8ff22f6                                 
Successfully rebased and updated refs/heads/master.

Check the history again, we should see the reorder task completed as expected.

% git log --oneline
74c674e (HEAD -> master) 2
1d418f5 3
8ff22f6 1

Delete commits

Suppose we have a simple git history like below. We want to delete the middle commits 2 and 4.

% git log --oneline
c31a95a (HEAD -> master) 5
8c4ed5b 4
0a628e3 3
e5ff4a4 2
a5247b8 1

Let use git rebase to solve this problem. First, go into interactive mode like before git rebase -i HEAD~4.

pick e5ff4a4 2
pick 0a628e3 3
pick 8c4ed5b 4
pick c31a95a 5

# ...

Then we can use the drop command to delete commits. Change the target commits with drop command as below.

drop e5ff4a4 2
pick 0a628e3 3
drop 8c4ed5b 4
pick c31a95a 5

# ...

Then save the changes and exit vim. We should see the success message.

% git rebase -i HEAD~4
Successfully rebased and updated refs/heads/master.

Then we can check the history for confirmation.

% git log --oneline                      
7f56ae1 (HEAD -> master) 5
1e17c1d 3
a5247b8 1

Change commit message

The git commit --amend command can be used to commit current changes into the last commit and also change the commit message. But this can only be used to change the latest commit. If we want to change the messages of previous commits, we should use git rebase.

Suppose we hava a git history like below. We want to change the middle commit message from 3 to xxx.

% git log --oneline                      
7f56ae1 (HEAD -> master) 5
1e17c1d 3
a5247b8 1

Just like before, let's go into interactive mode git reabse -i HEAD~2.

pick 1e17c1d 3
pick 7f56ae1 5

# ...

Then we change the pick command into reword, then save and exit.

reword 1e17c1d 3
pick 7f56ae1 5

Then a new window should be opened and we can make changes to the target commit message. Make the change, save and exit. Then we can check the result.

% git log --oneline   
c3aafbd (HEAD -> master) 5
738a05a xxx
a5247b8 1

Squash commits

During development, we may have a lot of commits. But we may want to squash them into one commit before pushing to remote repository. Let use rebase to solve this problem.

Suppose we have a simple history like below. We want to squash commits 2, 3, 4 into 1 commit.

% git log --oneline
1b4bfb8 (HEAD -> master) 4
b6930df 3
c85691e 2
17205b8 1

Let go into interactive mode git rebase -i HEAD~3

pick c85691e 2
pick b6930df 3
pick 1b4bfb8 4

# ...

Then we use squash command on these commits. This means we want to squash commits 4 and 3 into commit 2.

pick c85691e 2
squash b6930df 3
squash 1b4bfb8 4

Save and exit, we should see another window, which contains all the commit messages of these 3 commits.

We can change then into one commit message xxx. Save and exit, then let's check the result.

% git log --oneline   
15600d3 (HEAD -> master) xxx
17205b8 1

Another git rebase command fixup can also be used for this problem. The difference is that the base commit message(commit 2 in this case) will be used directly.

Edit commit

There is a more powerful rebase command called edit, we can use it to change any commit.

Suppose we have a history below. We want to change the commit 3.

% git log --oneline       
1b4bfb8 (HEAD -> master) 4
b6930df 3
c85691e 2
17205b8 1

Go into interactive mode git rebase -i HEAD~2, and tag the target commit with command edit.

edit b6930df 3
pick 1b4bfb8 4

Save and exit, the rebase process now stops. We are now at the target commit 3, and the rebase process waits for new changes.

Now let's reset commit 3, make new changes and then make a new commit.

% git reset HEAD^
% git status
% rm 3
% touch 3333
% git add .
% git commit -m 3333

OK, the new changes is done, we can continue the rebase process.

% git rebase --continue
Successfully rebased and updated refs/heads/master.

Check the history we can see the new changes now.

% git log --oneline
cdeae0c (HEAD -> master) 4
9767905 3333
c85691e 2
17205b8 1