Using Git Efficiently

2021-01-05

Introduction

There was a debate a few months back on Twitter about 10x developers, if they exist, what is the definition of such unicorn, or even if 10x developers are only bad developers who simply look good to management.

The truth is, without any prior knowledge, if we assume that effectiveness can be measured (whatever definition you want to put in that word), this distribution will most likely follow a bell curve over engineers. You the reader, and I the writer, are mathematically most likely to be in the middle of the curve. In other words: we are average developers.

If I, an average developer, want to get more effective, instead of only looking up at highly effective developers, we need to understand the characteristics of an average developer. This allows us to overcome the survivorship bias when fishing for advice.

What would an average developer look like? Depending on your definition of effective, it could mean:

If you recognize yourself in one of those bullet points, congratulation, you are an average developer! (Spoiler: everybody is average, just select your metric accordingly).

That said, there are heuristics you can follow to look less average within your peers, these tips won't make you make fewer mistakes, or make you smarter per se, but following them will help you introspect your biases and become a better engineer.

I've tried to make them as practical as possible.

Part 1: Using git effectively

This is probably the most important advice, doing pair-coding with junior developers, I've seen so many instances of the following anti-pattern:

<change a file>
:wq <quit text editor>

git add -A
git commit -m "Fix typo"
git push

<Click on re-request review on github>

This is probably the worse set of commands you can run, 95% percent of the time you will look fast and swift, but in the 5 other percent, you will look like a fool in front of your colleague reviewing your pull request.

Let's break it down to a more careful and methodic way to create new commits.

Tip 1: Proofread when stashing changes

The first advice is to never, ever, use git add -A. With -A parameter you never know what you are committing, you could still have a debug statement remaining, forgot to clean up the last issue you were working on, or simply staging files you don't mean to commit.

When you create a commit, not only make sure to know exactly which file you are committing, but also make sure you have read each line before staging them. A great tool to use is git with git add -p or git add --patch in its long-form.

# Prefer patch mode over --all:
git add -p

The -p parameter allows you to visualize each patch of a commit you are going to create. You'll know exactly which change you are going to commit. Make sure you are reading every single line of each patch!

In particular, before adding a patch, look carefully for:

Once you have staged your changes, don't hesitate to have a quick glance at the changes you made by running:

# Changes staged for commit:
git diff --staged

# Changes not staged for commit:
git diff

Make sure that you did not forget any new file in the staging area!

Tip 2: force yourself to write effective git messages

Now that you have reviewed the changes you made, you are ready to create a new commit. Here again, I advise against using any shortcut like git commit -m. Even if -m is swift, it encourages the developer to write unhelpful commit messages.

Prefer to use git commit in verbose mode:

# Always use verbose mode:
git commit -v

When writing a meaningful commit message, you will be able to see the full change that will be created. This serves two functions:

  1. Make sure again at a glance that you are committing what you want to commit. You don't need to read every line, that was done during staging, but have a glance at the patch and trust your subconscious mind to spot any irregularity
  2. When writing a commit message, you now have under your eyes the changes you are about to commit. This is a great way to write a relevant commit message.

There are already a ton of articles on how to write a great commit message, so I won't spend too much time on that. Basically, those good ideas can be summarized to:

When creating the commit message, especially for trivial changes, resist the urge to set an irrelevant commit title like:

You don't need to have a long commit message with a full body for each change, especially if the patch is trivial. Try to force yourself to always write a relevant commit message. This might take 5 seconds more than simply writing fix, but this will save countless time when another teammate will find the source of a bug in a tree of 10 000 commits.

Here are examples of better commit titles for trivial changes:

Tip 3: Never, ever, ever, use push --force

Your commit is now created locally. You can now push your changes with git push or git push -u origin <branch-name> if you are creating a new branch.

If you really need to push force (after a git commit -v --amend or a git rebase for example) prefer git push --force-with-lease over git push --force.

# Amend some changes to an existing commit:
git commit -v --amend

# Never push force:
git push --force-with-lease

with force-with-lease, if the remote branch has been modified from you last fetch, git will not override the changes. This is a great failsafe to avoid overwriting one of your colleagues' work.

Tip 4: Proof read again, and again

If your team is using Github, when creating the Pull Request don't hesitate to give a last glance at the diff, seeing your change in another context than your text editor is a great way to catch last minutes typos.

Automate your git workflow

Depending on which language you are developing in, the compiler might not be bundled with static analysis that could catch common pitfalls. For those languages, you might already rely on a linter on top of your test suite.

Linters not only serve the role of catching code errors or style violations, but they are also a great source of information for learning to write idiomatic code.

This article is not about linters (that a topic for another day), however, integrating linting and testing to your git workflow allows you to catch linter errors sooner rather than later.

Git has a feature called hooks that allows a developer to do actions based on different events. Those events could be creating a new commit, pulling or pushing some changes, etc...

For example, to run your lint suite before each commit:

cat << EOF > .git/hooks/pre-commit
#!/usr/bin/env bash

exec make lint
EOF

chmod +x .git/hooks/pre-commit

From now on, from now on, your lint suite run each time you create a new commit.

There are some cases when you don't want to run the lint suite. This can be if you know it will fail, or only want to create a temporary commit. You can skip the hook by adding -n to git commit:

# Skip hook and commit in verbose mode:
git commit -v -n

If your test suite is fast enough, you can also add it to a git pre-commit hook.

You don't need to type that much

You might have noticed, all those pieces of advice are here to slow you down and try to give your system 2 more chances to engage. Most mistakes happen because we are overconfidently only relying on our system 1. However programming is hard, and your System 1 has not evolved for to solve hard problems.

That said, you need to slow down your mind, but not necessarily your fingers. And to avoid that and help build these habits I suggest you create bash aliases for all those git commands. Here is an excerpt of my shell configuration:

# ~/.bashrc

alias gs="git status"
alias gd="git diff"
alias gds="git diff --staged"

alias ga="git add --patch"
alias gc="git commit --verbose"

alias gp="git push"
alias gpf="git push --force-with-lease"

# Push a new branch:
alias gpu="git push --set-upstream origin \`git rev-parse --abbrev-ref HEAD\`"

Note the use of long-form parameters in scripts, this is a great way to self-document each parameter.

Conclusion

This was part one of my serie of highly effective and practical tips to become a better engineer. I have many more areas of engineering to cover, so stay tuned for more tips on software engineering.