Xiaoxing's Notes

An Elegant Way of Managing Dotfiles


I was obsessed with the idea of managing dotfiles with git ever since the first time I found oh-my-zsh. I have used a lot of oh-my-xxx over the years, and none seams to stick. Maybe it's because the extra complexity they introduce for being a generic tool that tries to full fill everybody's needs. That's also why open source projects are like dogs, they were cute when they were puppies, then they grow up, being intimidating.

That's why I decided to reinvent the wheels again, and it turns out, a pretty simple and elegant solution came out of it. Here is how it works.

TL;DR

If you are the "shut-up-and-show-me-the-code" kind of guy, here you go. But I promise this is not going to be long. After all, simplicity is what I am trying to strike for.

The Solution

My solution is basically keeping all my dotfiles within a repo, and symlink them to the right locations. The reason for symlink instead of copying is for preserving the changes automatically when I (or something else) changed them.

There are 3 common scenarios for distributing dotfiles:

  1. You want a file. E.g. ~/.bashrc, ~/.tmuxrc etc...
  2. You want a folder. E.g. ~/.emacs.d
  3. You want a file and/or a folder in a path that's deeper then the home Dir. But you don't want to touch the rest of the files within that folder. E.g. ~/.config/fish (a folder), ~/Library/LaunchAgents/my-agent.plist (a file).

Number 3 seems to be pretty complicated to implement, but anything seems to be complicated for a human, there's a good chance for a simple solution for machines. I solved the puzzle with an elegant naming convention. I use two special characters as flags ~ and !. Within my repo, anything starts with ~ indicates that it's one of my target dotfiles. If it's a file, great, symlink to the home Dir. If it's a folder, then look inside to find the real target recursively. For example: a file ~Library/LaunchAgents/my-agent.plist indicates that the file my-agent.plist is going to be symlinked to ~/Library/LaunchAgents/my-agent.plist. When a folder starts with !, that means to symlink the whole folder. For example: I have a folder named ~!.emacs.d for my emacs config. They can also be nested inside other folders, like this one: ~Library/Application Support/!LaunchBar. You should be able to guess the meaning of that now.

All the magic was done under 30 lines of ruby script (could be shorter I suppose, but it's not a competition), which you can find here. Feel free to take it and let me know if you have any thoughts on it. Open up an issue will grab my attention efficiently.