How to Use Git


My opinionated guide to using git for version control.

Principles

  1. Developers are stewards of history
    • Keep the log clean; protect its integrity
  2. What goes on master stays on master
    • Never force push
  3. Tags are like photographs; snapshots of a moment in time
    • Tag things you want to remember and remove them from HEAD – don’t leave them there “just in case” – it’ll last longer
  4. Branches are short and sweet, like a limerick
    • … unless you maintain multiple versions of your product, in which case some of your branches are main-line for those versions
    • All other branches: git in, make your changes, and git out
  5. Rebase is your friend
    • No merge commits
    • Fast-forward merging only
Open all sections

Start in the right place

How
git checkout master
git pull --rebase
Why

This will make sure that you're on the master branch, that master is up-to-date, and that any stray commits you have lying around in your local repository's master branch are placed on top of the remote version of history, as is Good and Proper in the stewardship of your code's history.

git config --global pull.rebase true

This config setting will make rebase the default behavior for pull, in case you forget to add the --rebase flag.

Make a branch

How
git checkout -b {branch name}
Why

Making changes on a separate branch helps history stay clean by keeping all of your changes for a specific feature or bugfix together, giving you a place to amend or revert commits without leaving a trace in history, and finally submitting your changes for a review as part of a pull request process.

Stage your changes for commiting

How
git add one/path/at/a/time
Why

A quick shortcut to recursively add all of the changes in the current directory is git all ., but, by adding files individually, you have a chance to be thoughtful and make a deliberate decision about whether each file belongs in the commit. It can be tempting to take all of the changes at once, but we often end up touching many files during the development process and making changes that are not required in the final solution. These changes may be good changes to make, but if they aren't required for the current issue you're working on, it's probably best to make a separate commit.

Do a self-review

How
git diff --cached
Why

diff with the --cached option will show you all of your staged (added), but not yet committed, changes. This is a good opportunity to review what you've done, and make sure that each line that changed is correct and appropriate to be committed at this time.

Commit your changes

How
git commit
Why

Be sure to add a thoughtful comment about the changes you are making. If you have a ticket tracking system, it can be very helpful to include a reference to the ticket you are working on at the start of the comment, like this:

[issue-123] Adds a third solo to correct for insufficient cowbell

Add the one little change you missed

How
git commit --amend
Why

Sometimes, immediately after commiting changes, despite all the reviews, I do a git status and see a file I missed. Or I flip back to my editor and realize "Oh, if I change this, then I'm going to have to change that too." In these cases, there's no harm in modifying the commit I just made. I'll add the additonal changes and then use --amend to alter the previous commit, combining my staged but uncommitted changes with the changes in the previous commit. So if my last commit added two files and I have one more file staged, then the commit with two files is replaced by a commit with all three. In the commit history it now looks like I committed all three at once. My intent is clear, and the history reflects that intent. No need for a commit that just says "oops, I missed one."

Catch up on changes in master

How

Make sure you have all of your changes committed, then:

git checkout master
git pull --rebase
git checkout {branch}
git rebase master

Resolve merge conflicts during the rebase, if any, and then you're all caught up with master.

Why

This process first takes you back to the master branch, pulls and updates your local master branch with any changes from origin, then switches back to your branch and rebases, applying your changes on top of the new master history.

See everything you've changed compared with master

How
git diff master..{branch}
Why

This is especially handy after you've rebased on top of a new master branch HEAD to do a sanity check before submitting changes for a peer review. My branch may contain a number of separate commits, and this lets me see the changes holisticly

Recovering a lost commit

How
git reflog

Search the reflog for your missing commit. It helps if you remember the message, or date and time of the commit. When you think you've found it, check out that commit:

git checkout {SHA1 hash}

Be careful! You are now "detached," meaning you are not at the HEAD of a branch. Commits made from here are not part of a branch. (Becoming detached is a common way of losing commits, in fact). Review the code at this point. If you've found your missing changes, then create a branch:

git checkout -b {branch name}

Now you can continue working from your recovered commit, merge those changes into another branch, or just leave the branch as it is, safe in the knowledge that your changes were not permanently lost.

Why

The reflog is similar to a branch's log. The commit log you get from git log shows the ancestry of the branch, the changes that incrementally brought it to this point. Instead of a sequential list of changes to files, the reflog shows the sequence of git commands that got you to the current state. The commit information (e.g., author, message, hash) is also there, and that's what helps us find our lost commits. We can narrow down the reflog further by piping the output into grep to filter it down to just the commits:

git reflog | grep "commit:"

Share your changes

How
git push origin {branch}
Why

All of your commits happen in your local repository. To share changes with anyone else, they have to be pushed or pulled from one repository to another. The way this typically works is that each developer pulls from a shared remote repository, refered to as origin, and then pushes their changes back to origin where others can then pull them down.

Need to see it in pictures? You can see branching in action with this interactive visual tutorial.