This article and the source codes attached should be used as reference only.Please thoroughly test your implementation before making changes to production environment
Checkout our NEW Video Channel you can like and subscribe too!

Introduction

There are times where we need to squash a tail of short commits and tidy up our commit history.This is seen more on code base where we have to remotely check whether they are fine.Good example is a Jenkins pipeline file.Any small updates we make in codebase to check if the pipeline change are working we need to commit and push.

Now when you are ready to squash your commits together you have couple of options.Lets discuss this in the below section.

Method1: Using Rebase command

This is the orthodox way to squash any commits together.If you are new to VI this might be little tricky. Please follow the steps I mention below in order.

Do git log to see the list of commit we target to squash

$ git log
commit d65ac9eaaa5a9f92cc2182ccd3cb6e930981842b (HEAD -> master, origin/master)
Author: sayannayas <sayannayas@gmail.com>
Date:   Sun May 3 17:57:12 2020 +0530

    seventh commit

commit b6fa7c63c0a8563aff9b7b70f526003b7842e62f
Author: sayannayas <sayannayas@gmail.com>
Date:   Sun May 3 17:56:54 2020 +0530

    sixth commit

commit ab176dfc5d096a51bc9e63effc280f348b714581
Author: sayannayas <sayannayas@gmail.com>
Date:   Sun May 3 17:55:47 2020 +0530

    fifth commit

commit 64710907985cd4a2c331686d7fb0d24b9aabf2b0
Author: sayannayas <sayannayas@gmail.com>
Date:   Sun May 3 14:34:35 2020 +0530

    collapsed branch

    4th commit
   

here we want to squash fifth,sixth,seventh commit and create a new commit as collapsed commit 2 on top of commit 64710 (lets name it as target).

Now do

git rebase -i <commitid> //where commit id is of the target (64710)

once this command is entered, a VI editor will open which will show list of commit ids

pick ab176df fifth commit
pick b6fa7c6 sixth commit
pick d65ac9e seventh commit

# Rebase 6471090..d65ac9e onto 6471090 (3 commands)

carefully note that it shows all the 3 commit ids which we want to club to one. Now we need to select the commit with latest changes and mark it as pick and all the other as squash.

Those who are new to VI

press I to goto insert mode.Slowly move your cursor near "pick" text and replace it with "s" (meaning squash)
once done press escape

your final file will look like this

pick ab176df fifth commit
s b6fa7c6 sixth commit
s d65ac9e seventh commit

press :wq! and enter - this will write the changes into the file.VI editor will close after this

now a new window will popup, this is to edit the commit messages if you like.If you dont bother to change it just :wq! again

done. we have successfully rebased.

[detached HEAD 3153732] fifth commit
 Date: Sun May 3 17:55:47 2020 +0530
 1 file changed, 4 insertions(+), 1 deletion(-)
Successfully rebased and updated refs/heads/master.

if you do a git status now you will see message like this

On branch master
Your branch and 'origin/master' have diverged,
and have 1 and 3 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)

nothing to commit, working tree clean

let us understand this, so currently we are 1 commit ahead(the squashed commit) of origin/master. And because we have removed the previous three commits (which is still in origin/master) we have diverged our branch working master and origin/master.

Now we need to upload this commit rewrites to our remote repo.to do that git push needs to be forced.otherwise will get this error

To https://github.com/sayanawsit4/git-squash-merge-test.git
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to 'https://github.com/sayanawsit4/git-squash-merge-test.git'
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.

this is understandable as our head and remote head has diverged.So we need inform (force) remote git repo to take the cleaned up commit history and changes.

$ git push -f origin master
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 279 bytes | 279.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/sayanawsit4/git-squash-merge-test.git
 + d65ac9e...3153732 master -> master (forced update)

we are done now. Do a git log and check the commit history now.

$ git log
commit 315373283dcbe1c7561e93a32969fbab0943ad37 (HEAD -> master, origin/master)
Author: sayannayas <sayannayas@gmail.com>
Date:   Sun May 3 17:55:47 2020 +0530

    fifth commit

    sixth commit

    seventh commit

commit 64710907985cd4a2c331686d7fb0d24b9aabf2b0
Author: sayannayas <sayannayas@gmail.com>
Date:   Sun May 3 14:34:35 2020 +0530

    collapsed branch

    4th commit

Method2 :

This is a more unorthodox method.Many a times git rebase might be time consuming especally when you have big commit history and your inside corporate device that runs tons to antivirus.Follow the steps carefully

Do a git log to identify the commits we want to squash

$ git log
commit 5e39e623a905a3befd750ed99484c3457c4087c3 (HEAD -> master, origin/master)
Author: sayannayas <sayannayas@gmail.com>
Date:   Sun May 3 18:47:14 2020 +0530

    ten commit

commit 0ebcd4adec8f898a5db77f11b204b007c43495e0
Author: sayannayas <sayannayas@gmail.com>
Date:   Sun May 3 18:46:33 2020 +0530

    ninth commit

commit 2e39fd2963337e7f312298fcb6e779c9dd5fd050
Author: sayannayas <sayannayas@gmail.com>
Date:   Sun May 3 18:46:08 2020 +0530

    eight commit

commit 315373283dcbe1c7561e93a32969fbab0943ad37  -- target
Author: sayannayas <sayannayas@gmail.com>
Date:   Sun May 3 17:55:47 2020 +0530

    fifth commit

    sixth commit

    seventh commit

in this case we are going to squash eight,ninth,tenth commit together into 1 on top of commit hash 31537 (this is our target)

So in this we have to checkout the target commit(315373) without allowing to overwrite changes from the latest commit(5e39e62).To do this open your project in notepad++ as a workspace and then do a checkout. When notepad++ prompts to overwrite(as the checkout is forcing it do) just select NO.This will keep the changed files in ‘M’ modified state.Just hit save in all this modified files( all modified files will come with red sign in notepad++)

$ git checkout 315373283dcbe1c7561e93a32969fbab0943ad37
Note: switching to '315373283dcbe1c7561e93a32969fbab0943ad37'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at 3153732 fifth commit

you will get a message saying HEAD is detached, dont worry about this.Git is just complaining because we have checkout out a random commit which it has no control to increment HEAD.

As mentioned above,when notepad prompts dont change this files.Click No.Save the file. dont-modify.PNG

Now the the most important part.Follow along..

create a a temp branch out of this commit

$ git checkout -b temp
Switched to a new branch 'temp'

point master and temp branch to the same commit id

$ git branch -f master temp

git checkout master branch

$ git checkout master
Switched to branch 'master'
M       hello.txt
Your branch is behind 'origin/master' by 3 commits, and can be fast-forwarded.
  (use "git pull" to update your local branch)

commit the changes

$ git commit -m 'collapsed changes 2'
[master c859ae2] collapsed changes 2
 1 file changed, 4 insertions(+), 1 deletion(-)

delete temp branch

$ git branch -d temp
Deleted branch temp (was 3153732).

git push force changes to remote.

$ git push -f origin master
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 6 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 275 bytes | 275.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/sayanawsit4/git-squash-merge-test.git
 + 5e39e62...c859ae2 master -> master (forced update)

we are done. Lets check the git logs now

$ git log
commit c859ae2ccf6388c48b5d3207de77005651b59e2c (HEAD -> master, origin/master)
Author: sayannayas <sayannayas@gmail.com>
Date:   Sun May 3 19:25:01 2020 +0530

    collapsed changes 2

commit 315373283dcbe1c7561e93a32969fbab0943ad37
Author: sayannayas <sayannayas@gmail.com>
Date:   Sun May 3 17:55:47 2020 +0530

    fifth commit

    sixth commit

    seventh commit

We have successfully Squashed the changes to a single commit.

Conclusion

Here we learnt how to clean up commit history using orthodox method of rebasing.We also learnt unorthodox method of cleaning the commits using simple pointer mechanism and some quirky.Thanks for reading.

Reference

merge-detached-head

    Content