Squashing And Why You Should Do It
Squashing commits in git is something I originally would only do if my companies repo server like bitbucket or git has squashing upon merge enabled. However, it’s
something you can do with the cli and vim pretty easily, and it makes your PR’s look amazing when you have everything under one neat commit ready to be merged into master.
It also hides all those mistakes you made in those 300 commits of your feature branch…
I encourage you to squash BEFORE you open a PR into develop and master. Let’s see how it’s done!
TLDR
In VIM Editor
1
2
|
git rebase -i origin/master
:%s/pick/squash/g
|
Things To Know And Need
-
Do not perform this on your master branch
-
All the commands in this post will be demoed in vim editor. Because why nano?
-
Git installed. It was innevitable.
Git Rebase
What is a git rebase
? in simple terms, it’s merging one branch, onto another. Git merge will preserve your history as it happened, while rebasing will rewrite it.
This makes sense as to why we use rebase to squash, because rewriting your history is what we want to do.
A git rebase alone won’t do what we want, we need to be able to pick the commits we want to edit and squash. That’s where the -i
(--interactive
) option comes in.
A squash in git is simply an interactive rebase. we take our branch and commits, and rebase them on master or develop (whichever branch you’re merging into) and then
we pick which commits to squash. Let’s try it out.
I’ve made a repo for testing these commands, and we can see I’ve made five commits on the feature branch:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
[jayson@RyterINC Test-git-repo] git log
commit 9ae72b1e1188c1c83fb9110325d4d10134dbd0d2 (HEAD -> test-branch, origin/test-branch, origin/HEAD)
Author: Ryterinc <jjryter1@gmail.com>
Date: Mon Nov 22 23:02:03 2021 -0500
removing jenkins dir
commit 620650a7aa98001dc051a5bc2749e0a8e4035104
Author: Ryterinc <jjryter1@gmail.com>
Date: Mon Nov 22 23:01:36 2021 -0500
BLAH BLAH BLAH
commit eed2a6b23905e34d53398e67daa1ed6c673ce319
Author: Ryterinc <jjryter1@gmail.com>
Date: Mon Nov 22 23:01:19 2021 -0500
mooooore commits
commit 69671e5a524eb7da697cf51d220da5fb54df7d7c
Author: Ryterinc <jjryter1@gmail.com>
Date: Mon Nov 22 23:01:00 2021 -0500
another commit
commit 5fc7d53a7cacf5d2035f2a57b0eb61cf2dcbf1f2
Author: Ryterinc <jjryter1@gmail.com>
Date: Mon Nov 22 23:00:44 2021 -0500
heres a commit
commit 6870360f406cc96d5567ceac4142123d4d7352b4 (origin/master, master)
Author: Jayson Ryter <jjryter1@gmail.com>
Date: Sat Feb 8 20:01:06 2020 -0500
adding submodule
|
Now, it would be very sloppy and unproffesional of me to commit these five commits to master, especially with the messages provided. Let’s squash these into one and
make a meaningful message now that my development is done.
In my case, I want to merge into my master branch, so let’s rebase onto it interactively
git rebase -i origin/master
From here, we are taken to a commit page where we will decide which commits to keep and which to squash.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
pick 5fc7d53 heres a commit
pick 69671e5 another commit
pick eed2a6b mooooore commits
pick 620650a BLAH BLAH BLAH
pick 9ae72b1 removing jenkins dir
# Rebase 6870360..9ae72b1 onto 620650a (5 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
|
As you may notice, the word “pick” on the side of each commit. This means we take the commit as is. If we want to squash, we change the pick
to squash
and
that will squash the commit into the line above it.
For example:
1
2
3
4
5
|
pick 5fc7d53 heres a commit
squash 69671e5 another commit
squash eed2a6b mooooore commits
squash 620650a BLAH BLAH BLAH
squash 9ae72b1 removing jenkins dir
|
This will squash all the commits, into the first commit.
1
2
3
4
5
|
pick 5fc7d53 heres a commit
squash 69671e5 another commit
pick eed2a6b mooooore commits
squash 620650a BLAH BLAH BLAH
pick 9ae72b1 removing jenkins dir
|
This will squash the two squash lines, into the commits before each squash. So in this case, 620650a
is squashed into eed2a6b
and 69671e5
is squashed into
5fc7d53
.
If you have many commits and don’t want to manually change each line, you can try a bit of regex in vim:
:%s/pick/squash/g
This will replace all instances of the word pick
with the word squash
. Then you can choose your picks accordingly.
In vim, once we edit and choose which commits to pick and squash, we can then save this change with :wq
and we are presented with the opportunity to edit the
messages of all the commits that were merged into our one commit.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
# This is a combination of 5 commits.
# This is the 1st commit message:
heres a commit
# This is the commit message #2:
another commit
# This is the commit message #3:
mooooore commits
# This is the commit message #4:
BLAH BLAH BLAH
# This is the commit message #5:
removing jenkins dir
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Mon Nov 22 23:00:44 2021 -0500
#
# interactive rebase in progress; onto 6870360
# Last commands done (5 commands done):
# squash 620650a BLAH BLAH BLAH
# squash 9ae72b1 removing jenkins dir
# No commands remaining.
# You are currently rebasing branch 'test-branch' on '6870360'.
#
# Changes to be committed:
# modified: file-one
# modified: file-three
# modified: file-two
# deleted: jenkins
|
As you can see from the message, this is a combination of the five commits we made. since we don’t want to keep any of these messages, we can delete the messages
and create a new one.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
git rebase testing
- adding mock content to files
- removing empty dir
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Mon Nov 22 23:00:44 2021 -0500
#
# interactive rebase in progress; onto 6870360
# Last commands done (5 commands done):
# squash 620650a BLAH BLAH BLAH
# squash 9ae72b1 removing jenkins dir
# No commands remaining.
# You are currently rebasing branch 'test-branch' on '6870360'.
#
# Changes to be committed:
# modified: file-one
# modified: file-three
# modified: file-two
# deleted: jenkins
#
|
We can then save this with :wq
and our branch will be successfully rebased with the commits merged.
1
2
3
4
5
6
|
[jayson@RyterINC Test-git-repo] git rebase -i origin/master
[detached HEAD 79d554e] git rebase testing
Date: Mon Nov 22 23:00:44 2021 -0500
4 files changed, 8 insertions(+), 1 deletion(-)
delete mode 160000 jenkins
Successfully rebased and updated refs/heads/test-branch.
|
If we check out the git log
, we can see our new beautiful commit.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
[jayson@RyterINC Test-git-repo] git log
commit 79d554eb7e8f8fe7be49e2d0c08fac7b19a84f20 (HEAD -> test-branch)
Author: Ryterinc <jjryter1@gmail.com>
Date: Mon Nov 22 23:00:44 2021 -0500
git rebase testing
- adding mock content to files
- removing empty dir
commit 6870360f406cc96d5567ceac4142123d4d7352b4 (origin/master, origin/HEAD, master)
Author: Jayson Ryter <jjryter1@gmail.com>
Date: Sat Feb 8 20:01:06 2020 -0500
adding submodule
|
Let’s push our changes:
1
2
3
4
5
6
7
8
|
[jayson@RyterINC Test-git-repo] git push
To https://github.com/RyterINC/Test-git-repo
! [rejected] test-branch -> test-branch (non-fast-forward)
error: failed to push some refs to 'https://github.com/RyterINC/Test-git-repo'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
|
Don’t panic, because we changed our history git thinks our branch is behind in commits and wants us to
integrate via a git pull. Whenever you rewrite history in git and want to push, you have to use
the force option.
YOU SHOULD NEVER SQUASH COMMITS ON MASTER, FOR THIS REASON
Master should never have its history rewritten, but because this is our feature branch this is fine. Most master branches should have rewrite history disabled
to avoid anyone from accidently making this mistake.
Let’s do a force push:
1
2
3
4
5
6
7
8
9
|
[jayson@RyterINC Test-git-repo] git push -f
Enumerating objects: 9, done.
Counting objects: 100% (8/8), done.
Delta compression using up to 16 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (5/5), 492 bytes | 492.00 KiB/s, done.
Total 5 (delta 0), reused 0 (delta 0)
To https://github.com/RyterINC/Test-git-repo
+ 9ae72b1...79d554e test-branch -> test-branch (forced update)
|
Our branch is now updated and our sloppy test commits are now replaced with one clean commit that is ready to merge into master.
In Conclusion
rebasing is a fairly easy thing to do once you’ve tried it out a couple of times. I encourage you to make a test git repo to practice this, and to squash those
commits BEFORE you merge into your develop or master branches. Cheers!