How to automatically squash Git commits - uyuni-project/uyuni GitHub Wiki

This is a walkthrough of two very useful Git features that allow reviewers to track what changed between reviews but still allow the author to create a nice history just before merging with a single command.

We start with a branch that's behind master (HEAD points to how-to-autosquash, i.e. 2f2c7c7c403).

~uyuni/uy-master[how-to-autosquash]
% git log --oneline -n 4 master
7b5a8febf3b (origin/master, origin/HEAD, master) ACL method for RBAC namespaces in struts
b023419ebd4 Fix unauthorized routes in ApiHandler
2f2c7c7c403 (HEAD -> how-to-autosquash) Implement authorization filters for spark, struts and ajax routes
34f4db2d621 Add experimental config flag for RBAC

We change something and commit it.

~uyuni/uy-master[how-to-autosquash]
% echo '\nThis is my meaningful chang.' >README.md
~uyuni/uy-master[how-to-autosquash]
% git add README.md 
~uyuni/uy-master[how-to-autosquash]
% git commit -m 'Really good stuff in this commit.'
[how-to-autosquash 7482eccb264] Really good stuff in this commit.
 1 file changed, 1 insertion(+), 24 deletions(-)

Now my attentive colleague reviews the change and screams

there is a typo, we can't accept that!

Well, that's something that is easily fixed.

~uyuni/uy-master[how-to-autosquash]
% sed -i 's/chang\./change./' README.md
~uyuni/uy-master[how-to-autosquash]
% git add README.md 
~uyuni/uy-master[how-to-autosquash]
% git commit --fixup=HEAD # change should be added to the last commit later on
[how-to-autosquash 8eafcd9eda5] fixup! Really good stuff in this commit.
 1 file changed, 1 insertion(+), 1 deletion(-)

This time the reviewer is happy

LGTM! Great contribution! It was so easy to review the individual commits.

And he is right, the difference between the first and the second review is in a single commit.

~uyuni/uy-master[how-to-autosquash]
% git show                  
commit 8eafcd9eda5c824fbeea519a55b9a621526a00ab (HEAD -> how-to-autosquash)
Author: Alexander Graul <[email protected]>
Date:   Tue Feb 25 14:09:51 2025 +0100

    fixup! Really good stuff in this commit.

diff --git a/README.md b/README.md
index 54dc38cafcb..c633cd95195 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,2 @@
 
-This is my meaningful chang.
+This is my meaningful change.

Looking at the current branch, we now have two additional commits on top of 2f2c7c7c403, the commit that how-to-autosquash first pointed to.

~uyuni/uy-master[how-to-autosquash]
% git log --oneline -n 4
8eafcd9eda5 (HEAD -> how-to-autosquash) fixup! Really good stuff in this commit.
7482eccb264 Really good stuff in this commit.
2f2c7c7c403 Implement authorization filters for spark, struts and ajax routes
34f4db2d621 Add experimental config flag for RBAC

At this point we are almost ready. The code change is approved, all tests are good. There is just one tiny thing: there are still two commits. That's not really needed, the second just fixes a problem with the first. And the commit message is weird. Also, there were changes in master in the meantime:

~uyuni/uy-master[how-to-autosquash]
% git diff --name-only origin/master..HEAD
README.md
java/code/src/com/redhat/rhn/common/security/acl/Access.java
java/code/src/com/redhat/rhn/frontend/xmlrpc/api/ApiHandler.java
java/code/src/com/redhat/rhn/manager/user/UserManager.java
java/code/src/com/suse/manager/api/HttpApiRegistry.java
java/code/src/com/suse/manager/api/test/HttpApiRegistryTest.java
testsuite/features/support/http_client.rb

Let's fix that! Remember that I said we just need one command? Here it is:

~uyuni/uy-master[how-to-autosquash]
% git rebase --autosquash origin/master
Successfully rebased and updated refs/heads/how-to-autosquash.

For good measure, let's take a quick look at the git log again.

~uyuni/uy-master[how-to-autosquash]
% git log --oneline -n 5
ed8929931d1 (HEAD -> how-to-autosquash) Really good stuff in this commit.
7b5a8febf3b (origin/master, origin/HEAD, master) ACL method for RBAC namespaces in struts
b023419ebd4 Fix unauthorized routes in ApiHandler
2f2c7c7c403 Implement authorization filters for spark, struts and ajax routes
34f4db2d621 Add experimental config flag for RBAC

Rebasing interactively

To pick individual commits to be squashed, you can use git rebase interactively. When used this way, it opens a rebase TODO plan in a text editor. From there, you can explicitly decide what to do for each commit.

~uyuni/uy-master[how-to-autosquash]
% git rebase --interactive origin/master

This command opens the following text in a text editor. When saved, the command for each commit is applied, and the rebase is done accordingly.

fixup 8eafcd9eda5 fixup! Really good stuff in this commit.
pick 7482eccb264 Really good stuff in this commit.

# 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
# u, update-ref <ref> = track a placeholder for the <ref> to be updated
#                       to this position in the new commits. The <ref> is
#                       updated at the end of the rebase
#
# 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.
⚠️ **GitHub.com Fallback** ⚠️