Dotfiles without Symlinks

In my day-to-day as a developer I make use of a lot of different tools. Emacs for writing code. mbsync/isync for pulling down new emails. Vim for quickly editing files on remote hosts. Tmux for ssh’ing into servers. I like to configure this software to make it feel exactly right to me.

For example, my emacs configuration file is about 300 lines long and contains things like custom themes and fonts, making the UI as simple as possible and a bunch of little lisp snippets to help me automate repetitive tasks.

Over the course of their career a software engineer will need to setup new computers. If they don’t have a way of porting over their old configuration files to the new computer they will be wasting time every time they need to do this. So they think: “I know! I will just store my configuration files somewhere and sync it between each computer!”

Not a bad idea at all. There are a few ways to do this. Most end up using Dropbox or GitHub. My initial attempt at solving this was to create a folder containing all my files and then create symlinks in my home directory.

However, I quickly found that this was a pain in the ass. If you do a Google search for “why are symlinks bad” you will likely end up this cat-v article which sums up the issues with symlinks.

The problem with symlinks.

My main problem with them is that they break some foundational assumptions about the file system. If you open a symlink should you be transported to the directory containing the linked file?

What about double dot in the terminal – in the context of an open symlink does the double dot mean up a directory from the containing folder, or up a directory from the folder containing the symlinked file?

TL;DR: Symlinks are a kludge and in some cases they break simple file hierarchy semantics.

So how do we fix it?

Grin and bare it.

Git has around 1.5 million individual commands. One of those commands is –bare when initialising a new git repository.

By initialising a git bare repo in our home directory we can still track individual files, we can push them to a remote, and we can do it without having to resort to using symlinks.

So how does it work? Here are the few simple commands to get it set up.

cd ~;
git init --bare $HOME/.cfg

alias config='/usr/bin/git --git-dir=$HOME/.cfg/ --work-tree=$HOME'

config config status.showUntrackedFiles no

So now we are ready to add files to our repo. Adding that alias to your bash or zsh profile will be helpful for the future.

Adding some some files.

So now we can use start adding all of our configuration files.

config status
config add .emacs
config commit -m "Add .emacs config"
config add .config/nvim/init.vim
config commit -m "Add vim config"

Pushing to a remote.

config add remote origin <REMOTE_GITHUB_URL>
config push

If everything went well you should now see your changes in Github. Well done – we’re all sorted for now on this computer!


And to go full circle lets see how we can then clone the dotfiles to a new computer.

git clone --separate-git-dir=~/.cfg ~

By Adam Fallon

Leave a Reply