3 October 2020

This started as part of a contributing guide for one my projects, but I realised that it was generic enough to be more widely useful. Feel free to link to or modify it.

Using Git effectively is as much an art as it is a science. It’s powerful, but unfortunately it doesn’t guide you much in how to use it. I’ve been using Git for many years and maintain a project which uses it. Here’s an overview of my main recommendations.

Getting started

If you’re new to Git, the main thing I recommend is using a GUI:

  • SourceTree (free; Windows, Mac)
  • Git has some semi-official GUIs git gui and gitk, which can be installed as extra packages on Linux distros and via Homebrew for Mac. They’re a bit clunky but work fine. (free; Windows?, Mac, Linux)
  • gitui: a simple “terminal UI” (free; Windows, Mac, Linux)
  • GitKraken (paid; Windows, Mac, Linux)
  • lazygit: a terminal UI for more advanced users (free; Windows, Mac, Linux)

There is also the GitHub Desktop app, but I would only use it for very simple tasks—from what I’ve seen it’s a little too oversimplified.

For an in-depth guide to Git, try the Pro Git web-book.

Conventions

Unfortunately, knowing technically how to use Git isn’t enough! Each team often has its own conventions too, because they care different amounts about what the Git history looks like.

Some teams don’t care at all about the history, and some are super strict. Personally I find putting in at least a little effort makes it much easier when someone is looking back and trying to figure out when/why something changed. My advice is:

Try to make each commit represent a single, easily-describable change.

  • If you want to move code and modify it, move it in one commit then modify in another.
  • Try to keep functional changes separate from formatting changes or rewrites/refactoring.

Give each commit a good message. Do at least the following:

  • Have a single subject line, followed optionally by a blank line and any extra info in paragraphs or bullet points.
  • Keep all lines to less than 72 characters. You can wrap the body, but not the subject line.
  • (There are other conventions which are sometimes enforced, but in my opinion those two are the most important. For more info see the seven rules of a great commit message by Chris Beams.)

If you have a branch of multiple commits, some projects will ask you to make it free of errors, revertions, and fixes to your own changes, e.g. if you change your approach after a review. The reason for this is so the history “moves in one direction” and is clearer to understand.

  • If your change works as a single commit, you can repeatedly “edit” it with git commit --amend.
  • Alternatively, you can make separate commits and then squash them down to one using a soft reset and commit or an interactive rebase. (See below.) See also Rewriting History in Pro Git.
    • git reset --soft <base>
    • git rebase -i <base>
  • If you want multiple commits in your final branch, then look into fixups and auto-squashing, which allow you to alter past commits with ease.
    • git commit --fixup <commit>
    • git rebase -i --autosquash <base>

Note: Changing a branch’s history after you’ve already pushed it requires you to then force push it with git push -f.

Rewriting history can be a massive pain. In projects I maintain, I don’t often ask contributors to do it; instead I make heavy use of GitHub et al.’s “squash and merge” button for most PRs so they end up as one commit.

Useful tricks and commands

Squash multiple commits together

See the unfortunately huge variety of answers to Squash my last X commits together using Git.

In my experience there is little need to keep all the old commits’ information in the new message. So I would use ‘fixup’ instead of ‘squash’ if using interactive rebase, or otherwise delete and rewrite the message when soft resetting.

Fetch GitHub and GitLab pull requests

You can use Git commands to easily check out pull/merge requests on GitHub and GitLab.

On GitHub, the branch for PR #n is available at the ref refs/pull/n/head. On GitLab, the branch for MR !n is available at the ref refs/merge-requests/n/head.

To fetch these branches, you need to fetch this ref from the remote and map it to a local branch. The command for that is

git fetch REMOTE REF:LOCAL_BRANCH_NAME

For example, git fetch origin refs/pull/32/head:fix-warnings would pull PR #32’s branch from origin into my repo and call it fix-warnings.

To re-fetch the branch after it has been changed or rebased, use the force flag: git fetch -f ....