Making a git commit can be as quick as
git add --all && git commit -m "WIP". The problem with this approach is that we didn't check what changes we committed. Who knows what got committed that shouldn't have. Usually we find out when it's too late. We want to see the Git changes before we commit.
While working on an issue, I personally make a lot of changes that don't belong in the same commit. For that reason the approach of "now commit everything I have changed" rarely works for me.
Let's look at the things we don't want to commit first and then improve the way we make commits by reviewing what we commit first and how.
Things we don't want to commit
Some things that we don't want stored in git commits are:
- App secrets
- Tokens, passwords, personal information, etc. These are things we don't want to commit to the git history, especially public repositories. A bot will scrape our hosting provider credentials and spin up a lot of machines on our dime before we know what happened.
- Debugging code
- Some code may still be present in the project that helped debug an issue, such as a
console.log, or a
debugger-statement. While not necessarily harmful it creates a lot of output or hangs the process while running the app and test suites. It creates unwanted noise.
- Some code may still be present in the project that helped debug an issue, such as a
Removing these changes from commits isn't always as easy. Force pushing branches can mess up the team's flow and commits in public repositories don't disappear. Someone with a direct link can still access the old commits.
To go through the process of creating new app secrets, rolling those out across the app is very time consuming and cumbersome. Removing debugging code isn't as much trouble but it does require another commit, which adds noise to the git history. This doesn't help communicating in git.
Review what to commit
To avoid including things that shouldn't be committed and create better commits, let's look at ways to create commits.
This is the process I recommend going through when creating a commit:
- Start with reviewing changes:
- Are we about to stage files we should not be staging, such as credential files?
- Are there debug statements in the changes we should remove first?
- Are there changes that are not required to fix the bug or add the feature? In a commit where a bug is fixed, don't include code style changes in an unrelated part of the code.
- Finally, this is also a review for you, the committer, to double check the changes that were made are actually all necessary and correct. Are there tests for every logic change? Did we add or update the docs?
- Stage the changes that you approve and are necessary.
- Only after this review, commit the changes.
The main idea of this is that we look at what files we stage, but more importantly, we review the changes in each file before we stage them.
Let's look at some tools that help with creating git commits. These are alternatives to
git add --all command from the beginning of this post, which stages all changes: modifications, additions and deletions.
There are some basic tools that git provides us and there are third party tools we can use to make staging specific changes easier.
Git add file
git add command it's possible to add one file at a time with
git add path/to/file.md. This makes sure we don't add files we shouldn't commit, such as secret files, which is good.
$ git status On branch main Changes not staged for commit: modified: path/to/file.md $ git add path/to/file.md $ git status On branch master Changes to be committed: modified: path/to/file.md
But what about the changes in those files? We still don't know exactly what changes are being staged and will end up getting committed. We need a more detailed staging process.
Interactively stage changes
To review and select changes within a file we want to stage we can use
git add --patch. This opens a basic selection interface in the terminal with which we can select "hunks" to be staged. Hunks are groups of changed lines in a file that git can easily stage and unstage.
$ git add --patch diff --git a/app/models/blog_post.rb b/app/models/blog_post.rb index 8b13789..257cc56 100644 --- a/app/models/blog_post.rb +++ b/app/models/blog_post.rb @@ -154,6 +154,12 @@ class BlogPost < ActiveRecord::Base end + + def published? + binding.pry + Time.now >= published_at + end def active? (1/10) Stage this hunk [y,n,q,a,d,e,?]?
In the example above we run
git add --patch and it prompts us with the question if we want to "Stage this hunk". What's useful here is that we can see the "hunk" it's referring to in the preview above it, and review if it's any code we actually want to commit.
Choosing "y" for "yes", or "n" for "no", we can step by step stage changes or skip them. Git will take us through all the changes one "hunk" at a time until we've reviewed them all.
But maybe we spot an issue with the hunk. We can "q" for "quit" and open the file in our editor to fix the issue and then restart the process by running
git add --patch again.
This interface is quite limiting. If we don't agree with git's hunk boundaries we can't easily change them. It's not very easy to only stage one line or omit a line. If we stage the hunk in the example above we also commit some debugging code that will break things for us later.
A git GUI can help give us more control over what we commit. Apps that give us humans a more easy to digest interface for git and may even help with more advanced tools such as merge conflict resolution and rebasing.
Git GUIs are great! Don't let anyone tell you otherwise.
When making commits it's important to have a view of the current changes, stage them interactively, and commit knowing we only commit those changes that were selected. A GUI can display very detailed changes in a concise way, and the selection process for staging changes is more precise.
Personally I use tig most of the time in the terminal, and Fork any time I run into tig's limitations. To get started I recommend Fork, or a similar GUI app, as it's a very complete Git tool. Both tools give great overviews of git history and staged and unstaged files. It's possible to stage entire files in one go, or deep dive and only add the one specific line we want to stage and nothing else.
There are many other git GUIs, and no one tool is the best or only one you're allowed to use. Experiment, try them out, and use the tool that works best for you.
An example commit flow
When I am making a commit I often use tig in the terminal, but for convenience I'll use Fork in this example as it will look the same for everyone.
First I open the app, then I'm presented with the git history. After opening the "Local Changes" view I get an overview of all uncommitted changes.
In this example I want to stage the
env_helpers.rb file, but not all changes. There are changes in the same file for another feature I'm working on. If I stage the entire hunk I also stage the code I don't want to include. I could open the file in my editor again and remove the line temporarily, but then I should remember to restore it again later. (I'm going to forget that, aren't I?)
This is the process I go through in the video:
- In the left panel's "Unstaged Changes" box I look for the file I've just made changes to and want to commit.
- I open the file by clicking it and see the file changes in the right panel.
- There's a change on a line I do not want to commit, as indicated by the "TODO" comment.
- By selecting the line I do want to commit the Fork app gives me context specific actions to "Stage" the changes.
- I press the "Stage" button and only the selected change gets staged. The remaining unstaged file change is the line with the "TODO" comment I do not want to commit.
- I click on the file in the "Staged Changes" view in the left panel. Here I see the changes the one line change I just staged and nothing else. If these are all the changes I need, I can now commit them without also committing the unstaged changes.
I hope this peek into how I review my changes before committing helps you make better commits. It's a process to keep being aware of. When I haven't kept a close eye on what I commit, I have included and pushed things I shouldn't have. So keep an eye on it and let's make better commits together!
Review the changes you stage so you don't accidentally include any app secrets, debugging code, personal information, or anything else that shouldn't be committed.
Try out a GUI or any of the other tools I've listed in this post and let me know which is your favorite!