June 23, 2013 1

Versioning Dotfiles with Git

By in git

It’s become quite common to see ‘dotfiles’ repositories across GitHub, and for good reason. Versioning your home directory is a great way to maintain backups of your dotfiles, share with others, learn from others, and makes configuring a new machine a bit easier. Many users, however, don’t actually version their home directory. A common approach is to have a dotfiles repo somewhere (let’s say ~/dotfiles) and use a number of different ways to get the actual dot-files into the home directory itself: manually copy, manually symlink, or scripting the copy/symlink.

I’m not a fan of the copy approach. Any changes made to the actual dotfiles must to be manually replicated in the repo. I’m also not a fan of the symlink approach. It solves the problem of modifications but doesn’t account for new files. So why don’t we version the home directory itself?

I’m sure there are some good reasons not to version the home directory, and I’d love to hear them. I can’t think of concrete examples where having a ~/.git directory is a bad idea, but I’m sure there are a few. Regardless, here’s my current setup.

My Setup

My dotfiles repo is cloned under ~/dotfiles. Git has a setting (core.worktree) that configures where the working tree should be checked out. It expects a path that can be either absolute or relative to the ‘.git’ directory. To accomplish this, from my home directory:

$ git clone --no-checkout https://github.com/jasonkarns/dotfiles.git
$ git config core.worktree="../../"
$ git checkout master

This will do a normal git checkout, but instead of checking out the master branch to ‘~/dotfiles’, it checks out to the home directory itself. The repo itself (‘~/dotfiles’) is completely empty, except for the ‘.git’ directory.

Benefits

  1. Any changes to my dotfiles are known to git
  2. New files are known to git
  3. The home directory itself is not a git repo, yet it’s fully versioned.
    • git commands cannot be run under ~
    • git status is not displayed in my command prompt ($PS1) under ~
  4. To manage the dotfiles repo and run git commands, I must be in ‘~/dotfiles’. (I like the forced context switch.)

This setup has worked well for me. I have the benefits of a versioned home directory, without the annoyance of it being an actual repo (like seeing git status info in my command prompt).

Complications

There is one major complication: submodules. I use Vundle to manage my Vim plugins. Vundle itself is a git submodule under ‘dotfiles/.vim/bundle’. In order to run git submodule update, git requires that I be in the repo (duh) but also that I be in the working tree root. Since my working tree root is not in the repo, I get an error:

$ git submodule update
fatal: Not a git repository (or any of the parent directories): .git

To get around this, git has a feature wherein a plaintext file named ‘.git’ is placed in the root of the working tree. It contains just a single line: gitdir: /path/to/actual/repo/.git. While this file exists, the home directory itself becomes, for all intents and purposes, a proper git repo. I can run git commands directly from the home directory and even my command prompt picks up the git status info.

So, with ‘~/.git’ containing gitdir: /Users/<my_user>/dotfiles.git, I am able to properly run git submodule update and everything works! Of course, as long as this ‘.git’ file exists, my home directory is essentially a proper git repo, so once I’ve run any necessary commands, I simply delete the ‘.git’ file, and now I’m back to a plain, non-repo home directory!

 
 

Tags:

One Response to “Versioning Dotfiles with Git”

  1. Hi,

    Very interesting approach! When taking it into use I found that some commands, specifically “git pull –rebase” failed from the “~/dotfiles” directory.

    In my case I solved this by adding a small bash function:


    function gitd
    {
    local git_location_file=/home/${USER}/.git
    echo "gitdir: /home/${USER}/dotfiles/.git" > "${git_location_file}"
    git "${@}"
    rm "${git_location_file}"
    }

    An alias like below would be simpler, but it would work from every directory which would be confusing:

    alias gitd="git --git-dir=/home/${USER}/dotfiles/.git"

    This still enforces a context switch which I also find positive.

    Cheers!

Leave a Reply

You must be logged in to post a comment.