« Scott Nonnenberg


Better Git configuration

I like Git. I use it all the time. As I sometimes do, I recently took some time to really dig in, read through documentation, and review my global Git configuration. Welcome to my fourth stack improvements post!

It’s all Git

I started coding in the bad old days of plain filesystem copies and Visual SourceSafe with its exclusive locks on checkout. Even so, back then the concept of source control was so amazing to me I wished I had access to it when coding at home.

Later, at Cal Poly, I was exposed to Concurrent Versions System (CVS) as I worked on group projects. There were only so many group projects, so I never got very good at it.

During my Microsoft years, I used Team Foundation Server for source control during what we called “App Week,” where non-developers in Visual Studio would spend a whole week dogfooding the product to make sure it was ready to ship. But all of my personal programming during that time was with Subversion. It was free and easy to run locally. I used it to keep track of all my local changes over time.

Fall of 2010 was when things changed. I learned Ruby on Rails to develop a side project, and the comprehensive tutorial I went through covered Heroku and a new source control system: Git. It was amazing - I could treat it like it was hosted locally, but also interact with others. No exclusive locking, full productivity when offline, and intelligent merging. I was hooked.

Since then Git has truly taken off. It is the de facto standard for open source. It is supported by every major player in cloud source code hosting. It is supported by many GUI tools - dedicated source control tools, as well as code editors.

It’s important to know, and to know well.

Global config

Whether you know it or not, you’ve got global Git configuration. A .gitconfig file in your home directory. Most .gitconfig files have your name and email, as established by your average ‘getting started with Git’ tutorial. But there’s so much more configuration available for that file!

My entire global .gitconfig is available via a gist with inline comments. Jump directly there if you like, but here we’ll talk about each of the sections in a bit more detail.

Alias

The bread and butter of a custom .gitconfig is the [alias] section, where you can create your own commands. Feel like something is missing? Add it here! Something doesn’t quite work the way you want? Add your own version of it!

  • prune = fetch --prune - When working on a project where others push branches to the main repository, I end up with lots of random local branches. Prune deletes any local branch which has been deleted from the remote. It’s here because I always forget about it.
  • undo = reset --soft HEAD^ - If I’ve made a mistake in making a commit, this command sets things up the way they were before the commit. To be fair, usually I just amend the existing commit in this situation, since that keeps the commit message.
  • stash-all = stash save --include-untracked - Stash is extremely useful when someone randomly asks you to check out another branch, but you’re right in the middle of something. This command ensures that when you stash, you catch the new files you haven’t caught with a git add yet.
  • glog = log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' - Commit history shown via the default git log is space-inefficient, and doesn’t really focus on the most important information. This colorful, graphical display is easier to parse, especially when branching gets complex.

Merge

ff = only ensures that you get an error unless every merge is fast-forward. No merge commits, no joining together of two histories, just a smooth progression from commit to commit.

You might be wondering how you’d get real work done doing this! The answer is git rebase, a way to take a branch of the commit tree and attach it to a different part, giving it a new ‘base.’ It’s an extremely useful tool.

It’s how I update a pull request when it has conflicts with master. Essentially I get a chance to re-apply all of my commits in a future world, where others have made changes. I think it’s a better metaphor than “merge everyone else’s changes into my changes,” which is what you get if you merge the latest master into your branch.

This option is great because I will never accidentally create a merge commit. If I intend to create a merge commit I can force that behavior with an explicit git merge --ff.

conflictstyle = diff3 gives you a little more information when a merge conflict happens. Normally you get two sections - the intended changes from the ‘left’ and the intended changes from the ‘right.’ With this option you get a third section, the original changes before ‘left’ and ‘right’ tried to change it.

Commit

gpgSign = true ensures that all of your commits are signed by your GPG key. This is generally a good idea because there’s no verification of the user information in your .gitconfig file, which means that commits that look like they are from you could easily show up in someone else’s pull requests.

In fact, for a contract I once had to use someone else’s credentials because account and machine provisioning were taking too long. My pull requests were submitted through someone else’s account, but all the commits inside had my real name on them.

You can remove all question about where a commit came from by adding your public GPG key to your GitHub account. Your signed commits will have a little “Verified” badge on them.

Note:

  • If you have more than one GPG key, you can specify which you’d like to use with the user.signingKey option.
  • GUI apps won’t necessarily sign your commits even with this option set. You’ll need to explore the application’s options.
  • gpg-agent can save your passphrase for a time, making things a little easier. Use it!

Push

default = simple is an option you probably already have set. It makes it easier to push your local branch to a branch named the same thing in your target remote.

followTags = true is quite simple. Instead of manually pushing new tags with --follow-tags, you always send your local tags up along with a git push. I don’t know about you, but once I have tags created locally, I want them included when I push.

Status

showUntrackedFiles = all Normally, when you’ve added a new directory, but haven’t staged it yet with git add, your git status will just show the directory name. This has tripped me up quite a few times, since a new, large directory shows up as just one line. This option shows you all of the files underneath that new directory during a git status.

Note: This can make things slower in very large repositories.

Transfer

fsckobjects = true tells Git that you’d like it to do some extra checks when receiving or sending changes. Why do extra checks? You want to be alerted to evidence of data corruption sooner rather than later, right?

Note: This can make transfers a bit slower.

Diff Tool: icdiff

In addition to the built-in git diff command, Git allows you to specify an external tool to visualize your diffs. This collection of entries sets Git up to use icdiff to display the differences between two states of your repository:

[diff]
  tool = icdiff
[difftool]
  prompt = false
[difftool "icdiff"]
  cmd = /usr/local/bin/icdiff --line-numbers $LOCAL $REMOTE

You can use it just like normal: git difftool master branch

icdiff is interesting in that it attempts to replicate colorful, GitHub-style, split diffs right in your console. A little easier to read than the normal chunk-based diff style.

Note:

  • You might have some difficulty installing icdiff. Happily, there is a simple workaround.
  • Keep git diff in your back pocket - icdiff doesn’t seem to handle comparisons against /dev/null. For example, try git difftool --cached after you’ve staged a new file.

Bonus: More revisions!

You type git checkout master all the time, right? In that command, master is an example of a revision, specifically a shorthand referring to the latest commit in the master branch. These are the common revision formats:

# Check out a branch
git checkout branchname

# Check out a remote branch
git checkout remotes/origin/branchname

# Check out a specific commit
git checkout 158e4ef8409a7f115250309e1234567a44341404

# Check out most recent commit for current branch
git checkout HEAD

But it turns out that there’s a whole language to specify revisions. All of these operators apply to any of the nouns used above:

# ^ means 'first parent commit,' therefore the second-most recent commit in the master branch
git checkout master^

# If it's a merge commit, with more than one parent, this gets the second parent
git checkout master^2

# Same thing as three ^ characters - three 'first-parent' steps up the tree
git checkout master~3

# The first commit prior to a day ago
git checkout master@{yesterday}

# The first commit prior to 5 minutes ago
git checkout master@{5.minutes.ago}

You can find the whole set of supported revision formats here: https://git-scm.com/docs/gitrevisions. I was surprised how comprehensive it is!

Remember, you can use a revision with most Git commands. Try this: git glog master@{10.days.ago}..master

Go forth!

Start with my .gitconfig or just use it as an inspiration for your own customizations. You could even do a deep-dive like I did - the command list and the list of options are extremely educational.

Make Git maximally useful to you. Comfort with Git leaves more room for the hard bugs and the actual merge conflicts. Dig in!


Some good references:

I won't share your email with anyone. See previous emails.

It's me!
Hi, I'm Scott. I've written both server and client code in many languages for many employers and clients. I've also got a bit of an unusual perspective, since I've spent time in roles outside the pure 'software developer.'

NEXT:

Hard-won lessons: Five years with Node.js Apr 19

After five years working with Node.js, I’ve learned a lot. I’ve already shared a few stories, but this time I wanted to focus on the ones I learned the hard way. Bugs, challenges, surprises, and the... Read more »

PREVIOUS:

Think in alternatives (Dev productivity tip #5) Mar 21

I’ve been told that I’m a very productive developer. And I’m sharing how I do it. Welcome to the fifth in my developer productivity tips series: Think in alternatives. Your solution works, yes. Did... Read more »