Managing dot files with Git

Managing configuration files in your home directory on a POSIX system can be a pain when you often work on more than one machine, or when you accidentally remove or delete some useful option or file. It turns out that it’s beneficial to manage your configuration files via a version control system, which will allow you both to track the changes you make, and also to easily implement them on other machines. In this case, I’m going to show you how to do it with Git, but in principle there’s no reason most of this couldn’t work with Subversion or Mercurial.

Choosing which files to version

A good way to start is to take a look at the dot files and dot directories you have storing your carefully crafted configurations, and figure out for which of them it would be most important to track changes and to be able to rapidly deploy on remote systems. I use the following criteria:

  • Compatibility: Is the configuration likely to work on all or most of the systems on which you’re going to use it? If you’re going to check out your cutting edge .vimrc file on a remote Debian Sarge machine that hasn’t been updated since 2006, you might find that a lot of it doesn’t work properly. In some cases, you can add conditionals to configuration files so that they only load the option if it’s actually available. Similarly, you might not want to copy your .bashrc to all of your machines if you use a wide variety of them.
  • Transferability: Are you going to want exactly the same behaviour this file configures on all of your remote systems? If your .gitconfig file includes a personal handle or outside e-mail address, it might not be appropriate for you to clone that onto your work servers, since it’ll end up in commits you do from work.
  • Mutability: Are you going to be the only agent that updates this configuration, or will programs change it as well, for example to store cached file references? This can make updating a pain.
  • Privacy: If you’re going to put the file on GitHub or any other public repository service, does it contain private information? You probably shouldn’t put anything with API keys, SSH keys, or database credentials out in the ether.

With these criteria applied, it turns out there are configurations for three programs that I really want to be able to maintain easily across servers: my Vim configuration, my Git configuration, and my GNU Screen configuration.

Creating the repository

To start, we’ll create a directory called .dotfiles to hold all our configuration, and initialise it as an empty Git repository.

$ mkdir .dotfiles
$ cd .dotfiles
$ git init

Then we’ll copy in the configuration files we want to track, and drop symbolic links to them from where they used to be, so that the applications concerned read them correctly.

$ cd
$ mv .vim .dotfiles/vim
$ mv .vimrc .dotfiles/vimrc
$ mv .screenrc .dotfiles/screenrc
$ mv .gitconfig .dotfiles/gitconfig
$ ln -s .dotfiles/vim .vim
$ ln -s .dotfiles/vimrc .vimrc
$ ln -s .dotfiles/screenrc .screenrc
$ ln -s .dotfiles/gitconfig .gitconfig

Next, we drop into the .dotfiles directory, add everything to the staging area, and commit it:

$ cd .dotfiles
$ git add *
$ git commit -m "First commit of dotfiles."

And that’s it, we’ve now got all four of those files tracked in our local Git repository.

Using a remote repository

With that done, if you want to take the next step of having a central location where you can always get your configuration from any machine with an internet connection, you can set up a repository for your dot files on GitHub, with a free account. The instructions for doing this on GitHub itself are great, so just follow them for your existing repository. On my machine, the results look like this:

$ git remote add origin git@github.com:tejr/dotfiles.git
$ git push -u origin master

Note that I’m pushing using a public key setup, which you can arrange in the SSH Public Keys section of your GitHub account settings.

With this done, if you update your configuration at any time, first add and commit the changes to your local repository, and then all you need to do to update the GitHub version as well is:

$ git push

Cloning onto another machine

Having done this, when you’re working with a new machine onto which you’d like to clone your configuration, you clone the repository from GitHub, and delete any existing versions of those files in your home directory to replace them with symbolic links into your repository, like so:

$ git clone git@github.com:tejr/dotfiles.git .dotfiles
$ rm -r .vim .vimrc .screenrc .gitconfig
$ ln -s .dotfiles/vim .vim
$ ln -s .dotfiles/vimrc .vimrc
$ ln -s .dotfiles/screenrc .screenrc
$ ln -s .dotfiles/gitconfig .gitconfig

Finally, if you come back to use this machine later after you’ve tweaked these configuration files a bit and pushed them to GitHub, you can update them by just running a pull:

$ git pull

Making things easier

This ends up taking a lot of annoyances out of my day, as I know on any machine on which I frequently work, all I need to do is drop to my .dotfiles directory and run a git pull to get the most recent version of my configurations. This ends up being a lot better than manually running scp or rsync calls to keep things up to date.

13 thoughts on “Managing dot files with Git

  1. $ ln -s .dotfiles/vim .vim $ ln -s .dotfiles/vimrc .vimrc $ ln -s .dotfiles/screenrc .screenrc $ ln -s .dotfiles/gitconfig .gitconfig

    Can be written in a shorter form by using a for loop, and using a directory as the destination to ln. $ for x in vim vimrc screenrc ; do ln -s dotfiles/$x . ; done

    • Hi Morgan; I don’t think you tested that. It doesn’t work with my method because the file is named differently when it’s in the repo; the leading dot is removed because it doesn’t need to be hidden when it’s in a directory called .dotfiles. You would need to prepend the dot. You’ve also missed the .gitconfig file, and the leading dot on the target folder.

      The below works much better, but I feel it’s not as clear as what I suggest in my post for the purposes of my demonstration or for people who are relatively new to using the shell.

      for f in gitconfig screenrc vim vimrc; do ln -s ~/.dotfiles/$f ~/.$f; done

  2. Great post. I must agree, managing your dotfiles with git is awesome.

    I like your criteria for what can and should be tracked. I solve the privacy angle with two long running git branches. There is one called ‘personal’, which is free to share (and is pushed to github), and the other is ‘work’, which contains is based on ‘personal’, but contains some secret and semi-secret information specific to work. Then, whenever I want to add a change, I add it to the appropriate branch, depending on the sharability constraints. If I add it to the ‘personal’ branch I just remember to merge that into my ‘work’ branch. That helps keep things separated.

    Also, I wrote a little utility to help with managing dotfiles called dfm*.

    It’s up on github (incl. getting started instructions): https://github.com/justone/dotfiles Here’s an introductory post on my blog about it: http://endot.org/2010/10/16/dfm-a-utility-to-manage-dotfiles/

    I find it incredibly useful. It takes care of the symlinking, but it has other conveniences for updating and modifying your dotfiles.

    • It’s written in perl, but knowing that’s not important unless you want to modify it.
  3. FIrst at all, great post! But since I have a couple of additional .dotfiles (.ackrc, .Rprofile, .gitignore, .ctags, …) I evolved my for loop to this:

    find ~/.dotfiles/ -maxdepth 1 -name “.*” ! -path ~/.dotfiles/ ! -path ~/.dotfiles/.git -exec ln -sf {} –target-directory=$HOME \;

    This command creates a symbolic link of all files within name starting with a dot placed under ~/.dotfiles, except both ~/.dotfiles itself and .git directories.

  4. I am just starting out with customizing dotfiles and so on, but I’ve come up with the following bash script:

    !/bin/bash

    find ~/.dotfiles/ -maxdepth 1 -name ‘*’ ! -path ~/.dotfiles/ ! -path ~/.dotfiles/.git ! -path ~/.dotfiles/linkify -printf “%f:%p\n” | while IFS=”:” read FNAME FPATH do ln -svf “$FPATH” $HOME/.$FNAME done

    I’ve called it linkify and placed it inside my ~/.dotfiles folder. After I make changes to my dotfiles or pull new changes from the remote repo I run ~/.dotfiles/linkify and it takes care to make proper links.

    The -f flag forces it to overwrite the old links and the -v flag outputs info about the links it creates.

    The find filters prevents the script to create links to the dotfiles folder, to the .git folder or to the linkify script itself.

    Hope it helps someone :)

  5. Thanks for this!

    Is it a convention to make the .dotfiles directory hidden? I’m just learning about this stuff and saw some other blog posts where they don’t use the ‘.’ for the ~/dotfiles directory.

  6. Tom, thanks for this article.

    I too started managing my dot files with the manual steps you posted. Then I realized I was repeating myself a lot. I looked into using some other automation tools (vcsh, dropbox, other dot file scripts) but most of them were either too complex, required additional libraries (python, ruby) or didn’t have enough features.

    This led me down the path of creating dotm (Dot File Manager) which is exclusively in bash and has lots of features. The feature list and documentation can be viewed on github at https://github.com/brettbatie/dotfiles

    I hope others find it useful too.

Leave a Reply

Your email address will not be published. Required fields are marked *

You can use Markdown if you want.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>