Organizing and Sharing Dotfiles with Git

Posted on January 26, 2014

I use a lot of software to do my job. Bash, vim, git, ag, screen, slate, and a lot of other things that escape me right now. A lot of this software is highly customizable, and I add more configuration to it as I learn what does and doesn’t improve my workflow.

When I got my first full-time job after graduating from college, I ran into a problem: I now had two computers that I used to develop software, and they didn’t have the same configuration. I’d be zipping along writing code and try to invoke one of my favorite editor macros or shell aliases, then come to a screeching halt when it didn’t exist. I’d then fall out of my workflow and spend five minutes re-configuring things to match what I was used to. Sometimes it was worse — I’d realize that I could make something even better, and set that up, then do it to myself all over again when I used my home computer.

Eventually, I changed jobs, and realized I wanted to take all the things I’d added to my work machine’s configuration with me. So, like a lot of other people, I created a Github repo and stuck a bunch of config files in it. When I got a new computer, I’d clone the repo and set everything up.

Over the years I’ve made a few improvements to this pattern and collected a good number of useful configuration snippets, so I thought I’d spend some time writing about the ones that will probably be of the most use to other people.

Automating the installation process

At first, I just copied files out of the repo into the places programs expected them to be. This worked for getting a new machine set up quickly, but led to a problem: I’d forget to update things in the repo when I made changes, and would then have to do this by hand before pushing up to the remote to share with my other computers.

Fortunately, Unix systems have a good solution to this: symbolic links!

Here’s an example for a shell configuration:

#!/bin/bash

ln -s ~/dotfiles/git-completion.sh ~/.git-completion.sh
ln -s ~/dotfiles/bash_prompt ~/.bash_prompt
ln -s ~/dotfiles/bashrc ~/.bashrc

I have more than a few chunks of bash code floating around here, so it’s organized by function. The main bashrc file takes care of loading the others.

With links instead of manually-copied files, I can pull changes down from my hosted repository and have them take effect without having to remember to copy the files or get changes I’ve made locally back into the repo to avoid clobbering them with an update.

Vim plugins and Pathogen

I use Tim Pope’s excellent pathogen plugin to give each of my vim plugins a separate directory. This keeps them organized and makes it easy to add and remove plugins as my needs change, but it’s missing one thing: the ability to remember which plugins I use and install all of them in one go.

Git to the rescue! I use git submodules in my dotfiles repository to track my currently-used plugins and get them all installed. Submodules are a bit clunky to work with if you’re editing both the submodule and the parent project, but I so far haven’t made any edits to my vim plugins, so they’re perfect for this use case. The submodule system also has the added benefit of providing a single command to update all of the plugins to the latest version.

# Set up pathogen
mkdir -p ~/.vim/autoload
curl -Sso ~/.vim/autoload/pathogen.vim https://raw.github.com/tpope/vim-pathogen/master/autoload/pathogen.vim

# Link bundle directory and install submodules into it
ln -s ~/dotfiles/bundle ~/.vim/bundle
git submodule update --init
ln -s ~/dotfiles/vimrc ~/.vimrc

The vimrc contains the command necessary to load all of the pathogen-provided plugins:

call pathogen#infect()

Local overrides

Sometimes there are workflows or settings I need to use on one computer but not others. Most often, these are aliases I’ve set up for tasks specific to an employer’s environment. The easiest way I’ve found to deal with this is to create and load local configurations from the source-controlled files:

source ~/.bashrc_local  # Add whatever to this file!

This is also really useful for working on projects that expect to have sensitive data (database URIs, API tokens, and so on) available in the environment — things will always be available without me having to load a specific rc file before invoking a process, and I’m safe from accidentally committing and pushing these variables to a public repository.

Useful scripts and plugins

Vim

I won’t talk about writing a vimrc here, because mine is messy and Doug’s post is way, way better. I need to go through my vimrc and clean out some cruft, because there are things in there that I don’t think I understand. Instead, here are a few plugins that Doug doesn’t mention that I find really useful:

Bash

I know, I should use zsh like all the cool kids. One day I’ll switch over. A few neat things I’ve picked up that make life in the shell easier:

An OS X-specific hack or two

Git

One of the lesser-known features of Git is the repository template. If you provide the templatedir option to git init or git clone, Git will copy files from the template directory into the newly-created repository. This is very useful for things you wind up copying from project to project, like hooks and gitignore files.

I currently use this in the manner suggested by Tim Pope for auto-generating ctags when the working directory changes. Really great if you use vim and enjoy having ctags around to jump through your code.

Git also supports a global excludesfile option if you have things you want to ignore but not add to a repository-specific ignorefile (vim swapfiles, for instance): git config core.excludesfile ~/some/ignorefile.

Kevin Burke’s gitopen is a huge timesaver: open a browser tab to the current repository’s web page, or even open a new pull request from the command line!

Wrapping up

So, that’s how I keep my command-line life organized. I hope you found something useful here. If you want to inspect any of my working dotfiles, they live here.