Feel free to skip this brief rant and go straight to learning about squashing commits.
When I first started using Git it seemed very mysterious to me. This was mostly because I was used to VSS (Visual SourceSafe) and then TFVC (Team Foundation Version Control) before it was rebranded. I can't really remember how it worked, I just know that I was using it before Git was nicely integrated into Visual Studio. Now I don't even remember TFVC. I only remember that I had to deal with a whole bunch of gotchas, as I was learning to use it. I can't remember specifics, but I do remember thinking, "Why do I have to do this weird thing to make this work?"
Anyways, I'm all Git now. It's pretty much universal, so I've taken to understanding it more to remove the mystery. I say mystery, because my first professional experience with it was only through a git client, specifically Sourcetree. Sourcetree, like Microsoft products, tries to hide a lot of things from you in order to make it easier for you. This is great for the beginner or for doing simple things where everything goes right. Eventually, you will need to fix a big mistake and you won't be able to rely on SourceTree, because you don't really understand what it's doing.
My argument is that if you start with a simple command line way of using git commands, it makes life easier and allows you use pretty much use any client you want later on. Using the command line will allow you to understand the foundation of what the client is built on.
Git squash
Let's start with an example. I have a master branch for a killer app I'm working on. I think branching is a newb (rook) thing to do so I only work on the master branch, but I also want to keep it clean, because cleanliness is next to googliness. Here's my starting point:
Every killer app should have a killer README, so I'm going to get to work. I committed some pretty amazing lines of english into my README. Here's where I'm at after 3 commits:
Even though these are totally legit commit messages, some people might be confused by it and may not like to read them. It's currently local to my machine, so I still have some time to fix it and just make into one clean commit. I have the following options.
Option 1 - Rebase
I type the following to squash the 3 commits into 1:
$ git rebase -i
Now my editor will open and show the following commits that I can squash.
I'm shown the three local commits I made, all starting with the word 'pick'. The commented out sections in green below my commits, show the possible commands I can use. In our example, I want to 'squash' all the commits into one. So I'll change the text in the following way:
You'll see that I used 'pick' for the first commit and then 'squash' for the others. The reason for this is that git will run these commands from top to bottom. So, I'm telling git to 'pick' the commit with the commit hash 90a81fb and 'squash' the rest into the one I previously 'pick'ed.
Now I'll close that file to signal to git that I'm done rebasing. When I close the file, git will make my editor show me another file. It'll present me with a generated commit message, which defaults to taking all my commit messages. The comments in the green explain what I just said.
The whole purpose of this exercise is to make one clean commit message, so I'm going to delete the generated commit message and make a whole new commit message that's super cool.
Finally, I'll close the file, which will complete my rebase (squashing commits).
I'll just double check to see that nothing weird happened and that it's ready to be pushed to remote.
Everything looks good, nobody will know how many times I revised this bad boy and now I look like a rockstar 10x developer.
Option 2 - Squash
With this option, I have the scenario where I've given into peer pressure and have started working off of branches instead of directly on master. I'm currently working on a branch off of master, in this example, the readme-update branch.
I have the 3 new commits in my feature branch and only the initial commit in my master branch.
I'm ready to merge this over to master, but I just want one clean commit, so I'm going to checkout the master branch first.
$ git checkout master
Then I'm going to merge and squash my commits from my readme-update branch.
$ git merge --squash readme-update
At this point all my changes from the readme-update branch have been moved over and finally I have to commit them.
$ git commit
Now I'm presented with the messages from the squashed commits that I can keep if I want to.
In this example, I'm just going to use a one-liner message and close the file to complete the commit.
Finally, the squashing is complete and my master branch history is squeaky clean. Google, here I come.
My Preference
It really depends on how you like to work. I've always been a big fan of rebasing, once I understood how it worked, and I rebase all the time at my day job. So, my preference is git rebase -i, because I can use this locally whenever I want, whether I'm working on a branch off of master or directly on master. Also, If I'm working on a branch and rebase my commits to squash them, then I can clearly see that the history in my branch matches the history in the master once it's merged into master. If I don't use git merge --squash then I'll be creating an entirely new commit on the branch that I'm merging into. Of course, that may not matter. I'll most likely delete the entire branch anyway, when I'm done with it. In summary, I like rebasing, because it gives me more options like rewriting history on my remote branch when I need to pull the latest changes from master. It's a great tool to keep things clean.
Finally, that's just my preference. I hope I was able to shed some light on squashing commits in git.