Tips for using Git effectively

October 3, 2020

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

Using Git effectively is as much an art as it is a science.

If you’re not familiar with Git, there are lots of great tutorials and guides on the web. I can recommend the free Pro Git e-book, which is also a great reference for familiar users.

A Git history serves two purposes. Firstly, it is an archive of all previous versions of the code. Secondly, it provides a readable explanation of what changed and why.

This means that when preparing a branch to be merged, you should be mindful of how to present your changes to a future contributor or reviewer. This is especially true for public projects.

Every project will have a different workflow for their Git usage, so follow their guidance. I’ve collected some tips for what I consider general good practice for projects I maintain:

  • Try to make each commit represent a single, easily-describable change. Many branches and pull requests work well as just one commit, but larger changes may be clearer to read through in several commits.

    • 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 descriptive message. There is a great guide on the seven rules of a great commit message by Chris Beams.

  • Try to keep your branch commits clear 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 is clear as a single commit, one way to do this is to continually amend your commit as you work on 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 before pushing. (See below for details.) There’s a section in Pro Git on interactive rebasing: Rewriting History.

      • 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. There’s a great guide about these commands by Florent Lebreton.

      • 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. Some projects strongly dislike force-pushing, some live by it, and some don’t mind either way.

Other useful tricks

Squash multiple commits together

There is a great StackOverflow question about this with a huge variety of answers: 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 clear and rewrite the message to describe the overall change.

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 ....

← All posts