1. Overview

In this article, we’ll explore different ways to modify a Git commit.

2. Using amend

We can modify the latest Git commit by simply using the amend option. It replaces the most recent commit. We can modify the commit message and update the files included in the commit as well. Git considers the amended commit as a new commit.

Let’s try the amend option using an example. For simplicity, let’s update a file and commit with the message “Commit 1”. Now, let’s try to update the commit using the amend option:

git commit --amend

Executing the above command opens up an editor to include changes. Let’s update the commit message and save the changes. After closing the editor, we can see the updated commit as:

[master c0bc5d3] Amended Commit 1
 Date: Wed Jun 29 22:41:08 2022 +0530
 1 file changed, 1 insertion(+), 1 deletion(-)

We can also include staged changes while amending the commit. Let’s create additional changes and use the amend option to include them in the latest commit, again changing the commit message:

[master 0a1d571] Amended Commit 1 - Added new file
 Date: Wed Jun 29 22:41:08 2022 +0530
 2 files changed, 2 insertions(+), 1 deletion(-)
 create mode 100644 README2

In case we just want to add the staged changes without updating the commit message, we can use the no-edit option:

git commit --amend --no-edit

Hence, we can see that the amend option is a convenient way to add changes to the most recent commit. Now, let’s explore different ways to update older commits in our Git history.

3. Using rebase

We can move a sequence of commits to a new base using the rebase command. Git internally creates a new commit for each old commit and moves to the specified new base.

Using the -i option with the rebase command starts an interactive session. During this session, we can modify each commit if required using the below commands:

  • pick (p) -> include the specific commit
  • squash (s) -> merge the commit with the previous commit
  • drop (d) -> remove the specific commit
  • reword (r) -> include the commit and update the commit message
  • edit (e) -> include the commit with an option to update the files included as well

Let’s try updating the commit history in our example using the above commands. At this step, git log shows the following commits:

commit 5742fcbe1cb14a9c4f1425eea9032ffb4c6191e5 (HEAD -> master)
Author: #####
Date:   Fri Jul 1 08:11:52 2022 +0530
    commit 5
commit e9ed266b84dd29095577ddd8f6dc7fcf5cf9db0d
Author: #####
Date:   Fri Jul 1 08:11:37 2022 +0530
    commit 4
commit 080e3ecc041b7be1757af67bf03db982135b9093
Author: #####
Date:   Fri Jul 1 08:11:18 2022 +0530
    commit 3
commit d5923e0ced1caff5874d8d41f39d197b5e1e2468
Author: #####
Date:   Fri Jul 1 08:10:58 2022 +0530
    commit 2
commit 1376dc1182a798b16dc85239ec7382e8340d5267
Author: #####
Date:   Wed Jun 29 22:41:08 2022 +0530
    Amended Commit 1 - Added new file

Suppose we want to change the modifications committed after the commit “Amended Commit 1 – Added new file”.

Let’s set the above commit as the new base:

git rebase -i 1376dc1182a798b16dc85239ec7382e8340d5267

This opens an editor, where we can make changes as required:

pick d5923e0 commit 2
pick 080e3ec commit 3
pick e9ed266 commit 4
pick 5742fcb commit 5
# Rebase #####..### onto #### (4 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
# 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.
#
# Note that empty commits are commented out

Now, let’s try a few commands to update the Git history:

reword d5923e0 commit 2-updated
edit 080e3ec commit 3
squash e9ed266 commit 4
drop 5742fcb commit 5

After the first command, we’ll see the output:

[detached HEAD 178e8eb] commit 2-alter
 Date: Fri Jul 1 08:10:58 2022 +0530
 1 file changed, 1 insertion(+)
Stopped at ######...  commit 3
You can amend the commit now, with

  git commit --amend 

Once you are satisfied with your changes, run

  git rebase --continue

Now, as specified in the Git output, we execute the git commit –amend command and make a few changes:

commit 3          
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Fri Jul 1 08:11:18 2022 +0530
#
# interactive rebase in progress; onto 1376dc1
# Last commands done (2 commands done):
#    reword d5923e0 commit 2-updated
#    edit 080e3ec commit 3
# Next commands to do (2 remaining commands):
#    squash e9ed266 commit 4
#    drop 5742fcb commit 5
# You are currently splitting a commit while rebasing branch 'master' on '1376dc1'.
#
# Changes to be committed:
#       modified:   README
#       modified:   README2

After closing the editor, we get the following output:

[detached HEAD 9433120] commit 3 - updated
 Date: Fri Jul 1 08:11:18 2022 +0530
 2 files changed, 3 insertions(+), 1 deletion(-)

We can see here that we’ve not only updated the commit message but also included one more file as part of the commit.

Next, we need to execute the git rebase –continue command to move to the next update.

Our next step involves the squash of commit 4 with commit 3, and it opens up to the below editor:

# This is a combination of 2 commits.
# This is the 1st commit message:
commit 3 - updated
# This is the commit message #2:
commit 4
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# Date:      Fri Jul 1 08:11:18 2022 +0530
# interactive rebase in progress; onto 1376dc1
# Last commands done (3 commands done):
#    edit 080e3ec commit 3
#    squash e9ed266 commit 4
# Next command to do (1 remaining command):
#    drop 5742fcb commit 5
# You are currently rebasing branch 'master' on '1376dc1'.
#
# Changes to be committed:
#       modified:   README
#       modified:   README2

After adding modifications to the above editor, we get the below output:

[detached HEAD 917c583] commit 3 - squashed with commit 4
Date: Fri Jul 1 08:11:18 2022 +0530
2 files changed, 3 insertions(+), 1 deletion(-)
Successfully rebased and updated refs/heads/master.

Finally, we had requested to drop the commit 5, which doesn’t require any further modification from our end.

4. Analyzing Logs

After performing the above steps, we can see the output from git log as:

commit 917c583d5bb02803ee43cf87a2143f201c97bbe8 (HEAD -> master)
Author: #######
Date:   Fri Jul 1 08:11:18 2022 +0530
commit 3 - squashed with commit 4
commit 4
commit 178e8ebec178c166d1c9def2d680f41933eba29b
Author: #######
Date:   Fri Jul 1 08:10:58 2022 +0530
commit 2-alter
commit 1376dc1182a798b16dc85239ec7382e8340d5267
Author: #######
Date:   Wed Jun 29 22:41:08 2022 +0530
Amended Commit 1 - Added new file

Here, we can make a few observations:

  • commit 5 is removed
  • commit 4 is merged with commit 3
  • commit 2 message is updated

In the final log, we can see that the ids for commits are changed now. This is because Git has replaced the previous commits and created new commits with modifications.

We can use the reflog command to view the reference logs related to the current HEAD. It includes the history of all updates related to Git commits.

Let’s use the reflog command to observe the git history for our example:

917c583 (HEAD -> master) HEAD@{0}: rebase -i (finish): returning to refs/heads/master
917c583 (HEAD -> master) HEAD@{1}: rebase -i (squash): commit 3 - squashed with commit 4
9433120 HEAD@{2}: commit (amend): commit 3 - updated
f4e8340 HEAD@{3}: commit (amend): commit 3 - updated
fd048e1 HEAD@{4}: commit (amend): commit 3 - updated
39b2f1b HEAD@{5}: commit (amend): commit 3 - updated
f79cbfb HEAD@{6}: rebase -i (edit): commit 3
178e8eb HEAD@{7}: rebase -i (reword): commit 2-alter
d5923e0 HEAD@{8}: rebase -i: fast-forward
1376dc1 HEAD@{9}: rebase -i (start): checkout 1376dc1182a798b16dc85239ec7382e8340d5267
5742fcb HEAD@{10}: commit: commit 5
e9ed266 HEAD@{11}: commit: commit 4
080e3ec HEAD@{12}: commit: commit 3
d5923e0 HEAD@{13}: commit: commit 2
1376dc1 HEAD@{14}: commit (amend): Amended Commit 1 - Added new file

5. Conclusion

In this article, we’ve explored different ways to alter Git history. However, we should exercise caution while using these options, as it may lead to lost content as well.