chielkunkels

published on Saturday, August 16, 2014:

dotfiles 101 - git

First off I just want to apologise for this article taking so long to appear, but I’ve been really busy with a new job and also putting off finishing this one… but here it is - finally!


This article is part of a series; the first part gives a general introduction about dotfiles and why it’s a good idea to maintain your own, the second part goes into more depth with regards to bash.

In this part we’ll look at fleshing out your git config a bit more.

Before we start

In the examples I will show you how to modify git’s config files directly, but if you don’t like to edit them manually, you can manipulate all the options like so:

$ git config --global <option> <value>

Starting out

There’s 3 levels on which git allows you to configure options:

In this article, we’ll be changing the global options situated in the user’s home directory. Let’s go ahead and create it:

$ touch ~/.gitconfig

So, who are you again?

First off, we’ll want to tell git who we are. This information will be used when you create commits. Since you’re using github for your dotfiles repo, you’ll want to use an email address here that is registered with github—this will help map your name to your github username.

[user]
    name = Chiel Kunkels
    email = chiel@kunkels.me

Since these details are your identity, this is another upside of keeping your own dotfiles—you’ll always have your identity set up and ready to go.

Ignoring project-agnostic files

A lot of editors and operating systems generate files that are not related to your project at all. In cases like these you don’t really want to stick them in a project’s local .gitignore. Not to worry—a solution is nigh!

core.excludesfile allows you to specify a global .gitignore, let’s begin by specifying it’s location:

[core]
    excludesfile = ~/.gitignore

As you can see, this will expect a file in your home directory called .gitignore, but you can put it anywhere and call it whatever you like. I like consistency so I named it .gitignore, and since my config is user-specific, I keep it in my home directory. Let’s crank it open:

$ vim ~/.gitconfig

Since I’m using OS X and vim a lot I’ve specified 2 files generated by those for now:

.DS_Store
.netrwhist

I know there’s a bunch more files that you can ignore, but I’ll leave it up to you guys and gals to figure out what you want to ignore. For more information about ignoring files, check out github’s excellent help article.

Newline settings

Everybody knows (I hope) that various operating systems handle their line endings differently. Windows uses \r\n (carriage-return followed by line feed) whereas unix just uses \n (line feed). This can cause some issues while handling versions of your files, since technically the files are different. Git has some settings to handle this, so let’s go ahead and add them:

[core]
    autocrlf = input
    safecrlf = true

Since I only develop on OS X and linux, using input for the core.autocrlf works well for me since this means, as far as I know, that it uses whatever character is input by my operating system.

I definitely don’t know all there is to know when it comes to line endings and git, so I’m not going to pretend I do. These options have served me well so far but if you want to know more, I recommend hopping over to github’s help to read this article.

Pushing

Depending on your version of git, the default behaviour of git push will differ slightly. In git 1.x it’s set to matching which means all local branches that have a branch of the same name on your remote will get pushed.

In git 2.x it defaults to simple, meaning it will push to the upstream of your current branch, with an added safety measure of checking if your branch name is the same.

Personally, I am not a big fan of either of these behaviours. The matching behavior pushes too much stuff for my liking and the simple option restricts me by not allowing me to call my local and remote branches whatever I please.

[push]
    default = upstream

The upstream option gives me the best of both worlds—it pushes the current branch to it’s remote tracked branch, without checking if the name is the same or anything. Perfect.

Rebasing

I know that rebasing can be a somewhat controversial subject and that a lot of people prefer merging, but in my opinion both of these methods have their place as long as you know what you’re doing.

I’ll be the first to admit it’s a bit of a brainfuck whilst getting used to it, but once you get past that, it’s awesome.

[branch]
    autosetuprebase = always

[branch "master"]
    rebase = true

Basically what these options do is make sure that when you git pull, it does so with --rebase. For some, this will be annoying since you won’t be able to pull if you have a dirty working tree, but it will avoid creating useless merge commits.

I’m personally very pro-keeping-history-relevant, so for me this works out great, especially when using git log --graph, since it won’t have lots of “branches” which have no relevance.

Aliases

Ahh, aliases… what would would I do without you?

Aliases have, to my mind, two uses: frstly, they can make commands shorter, saving you some keystrokes and secondly they make the output of these commits a bit more useful.

Without further ado I’m just gonna spill it:

[alias]
    b = branch
    t = tag
    d = diff --stat -p -C
    ds = diff --staged --stat -p -C
    ci = commit -v
    co = checkout
    st = status -sb
    wc = whatchanged --abbrev-commit --date=relative --date-order --pretty='format:%Cgreen%h %Cblue%ar %Credby %an%Creset -- %s' -n 45
    ll = log --graph --date=relative --topo-order --pretty='format:%C(yellow)%h%C(yellow)%d %Cblue%ar %Cgreenby %an%Creset -- %s'
    guilt = "!f(){ git log --pretty='format:%an <%ae>' $@ | sort | uniq -c | sort -rn; }; f"

As you can see, a few of these commands are simply just for shortening, while others make the output a bit more… usable. I’m not going to explain what every single alias does, I suggest you just copy them over and give them a go!

Merging & diffs

Merging and dealing with diffs are two very common operations in git, so it makes sense to set that up properly.

[merge]
    tool = vimdiff

[mergetool]
    prompt = false

[diff]
    renames = true
    tool = vimdiff

[difftool]
    prompt = false

The above options will set vimdiff as your merge and diff tools, turn off prompts before showing those tools and show renamed files in diffs; all in all smoothing out your workflow a bit when dealing with these scenarios.

Painting rainbows

I FUCKING LOVE BRIGHT COLOURS.

No but seriously, colours on the command line really help distinguish things and make it a lot easier to parse the information on your screen, which in my opinion is critical when you’re trying to get to grips with the command line.

You can, of course, use whatever colours you want, but here’s the way I have things set up:

[color]
    ui = auto
    branch = auto
    diff = auto
    status = auto

[color "branch"]
    current = yellow reverse
    local = yellow
    remote = green

[color "diff"]
    meta = blue bold
    frag = magenta bold
    old = red
    new = green

[color "status"]
    added = blue bold
    changed = green bold
    untracked = cyan bold

This adds colour to a variety of different outputs from various git commands, yay!

In summary, aka TL;DR

Putting things from this article together, should leave you with a config that looks a little bit like this:

[user]
    name = Your Name
    email = you@your.name

[core]
    excludesfile = ~/.gitignore
    autocrlf = input
    safecrlf = true

[push]
    default = tracking

[branch]
    autosetuprebase = always

[branch "master"]
    rebase = true

[alias]
    b = branch
    t = tag
    d = diff --stat -p -C
    ds = diff --staged --stat -p -C
    ci = commit -v
    co = checkout
    st = status -sb
    wc = whatchanged --abbrev-commit --date=relative --date-order --pretty='format:%Cgreen%h %Cblue%ar %Credby %an%Creset -- %s' -n 45
    ll = log --graph --date=relative --topo-order --pretty='format:%C(yellow)%h%C(yellow)%d %Cblue%ar %Cgreenby %an%Creset -- %s'
    guilt = "!f(){ git log --pretty='format:%an <%ae>' $@ | sort | uniq -c | sort -rn; }; f"

[merge]
    tool = vimdiff

[mergetool]
    prompt = false

[diff]
    renames = true
    tool = vimdiff

[difftool]
    prompt = false

[color]
    ui = auto
    branch = auto
    diff = auto
    status = auto

[color "branch"]
    current = yellow reverse
    local = yellow
    remote = green

[color "diff"]
    meta = blue bold
    frag = magenta bold
    old = red
    new = green

[color "status"]
    added = blue bold
    changed = green bold
    untracked = cyan bold

So there it is - a git config which will hopefully help people on their quest of conquering git.

:wq