Git: Review changes before committing
Tags: git, github, programming, code
Published: 2020-10-26 12:52:32

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.

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 `print`/`puts`, a `console.log`, or a `binding.pry`/`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.

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][communication 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.

## Commit tools

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
Using the `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.

```sh
$ 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.

```diff
$ 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.

### Git GUI

A git <abbr title="Graphical User Interface">GUI</abbr> 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](https://git-scm.com/downloads/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?)

{% vimeo 462944661 %}

This is the process I go through in the video:

1. In the left panel's "Unstaged Changes" box I look for the file I've just made changes to and want to commit.
1. I open the file by clicking it and see the file changes in the right panel.
1. There's a change on a line I do not want to commit, as indicated by the "TODO" comment.
1. By selecting the line I do want to commit the Fork app gives me context specific actions to "Stage" the changes.
1. 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.
1. 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.

## Conclusion

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][twitter] which is your favorite!

[communication in git]: https://tomdebruijn.com/posts/git-is-about-communication/
[tig]: https://jonas.github.io/tig/
[Fork]: https://fork.dev/
[twitter]: https://twitter.com/tombruijn