Git, Dotfiles, and Hardlinks
One handy use for Git is keeping track of your dotfiles – all of those configuration files that live inside your home directory like
.vimrc, et cetera.
A typical first approach often winds up looking something like this:
~ $ git init dotfiles Initialized empty Git repository in /home/aiiane/dotfiles/.git/ ~ $ cd dotfiles ~/dotfiles $ ln -s ~/.vimrc . ~/dotfiles $ git add .vimrc ~/dotfiles $ git commit -m "Track my vim config file"
All seems well until you look at the diff for the commit you just made and see something like this:
diff --git a/.vimrc b/.vimrc new file mode 120000 index 0000000..6ba8edc --- /dev/null +++ b/.vimrc @@ -0,0 +1 @@ +/home/aiiane/.vimrc \ No newline at end of file
As it turns out, Git knows about symlinks and thus faithfully records the symlink as just that – a symlink – instead of recording the contents of the symlinked file. Oftentimes this leads to the thought of “well, if Git knows symlinks, then I can probably use a hardlink instead.” And thus the second attempt generally continues something like this:
~/dotfiles $ rm .vimrc ~/dotfiles $ ln ~/.vimrc . ~/dotfiles $ git add .vimrc ~/dotfiles $ git commit -m "Hardlink in my config file"
And this seems at first to work – looking at the diff, you see the contents of your config file being added; you push your commit to GitHub or whatever and your config file shows up properly there…
…until you try to have Git update the file. Perhaps you made a change somewhere else and now want to
git pull, or perhaps you made a change locally that you decided you didn’t want and so you use
git checkout .vimrc in your repo to change it back to your committed version. At this point you discover that while it seems to change the file in your repo, it doesn’t update the file in your home directory – the hardlink has been broken.
The reason for this is that Git never modifies files in the working tree – instead, it unlinks them and then recreates them from scratch. This inherently breaks any hardlinks that might have been present.
The third attempt is generally what winds up working, when you realize that what you can do does actually use symlinks, but rather than symlinking files outside of the repository into it, doing it the other way around: symlinking files inside the repository out of it, into your home directory:
~/dotfiles $ rm ~/.vimrc ~/dotfiles $ cd ~ ~ $ ln -s dotfiles/.vimrc .
Since most programs tend to handle symlinks transparently (unlike Git), this lets you use Git to update the actual copy of the file in the Git working tree, and have those changes reflected in the path where your programs expect to find it.
An alternative approach
Astute readers may notice that there is another possibility: why not just make your home directory the Git working tree?
~ $ git init Initialized empty Git repository in /home/aiiane/.git/ ~ $ git add .vimrc ~ $ git commit -m "Track vimrc"
While this does work, it has its own drawbacks. The most significant is probably the large number of things in your home directory that you typically don’t want to track which clutter up
git status. For those who take the home-as-worktree path, a logical solution to this problem is to just ignore everything by default:
~ $ echo "*" > .gitignore ~ $ git add -f .gitignore ~ $ git commit -m "Ignore all by default"
Then only things you’ve explicitly added (and you’ll need to use
git add -f the first time you add each file) will be tracked. If you do this, however, it’s harder to tell at a glance whether or not a given file is being tracked, and new files that you might want to add won’t stand out. There is a way to check, though:
~ $ git ls-files .gitignore
The other main potential drawback of using your home directory as a working tree is that it effectively requires you to version the same files on every machine – since Git doesn’t really do partial checkouts gracefully, once a particular path in your home directory is added, it’ll be tracked anywhere that pulls the commit which added it. Most of the time this probably won’t be an issue – generally if you want to track something, you want it the same everywhere – but it’s something to bear in mind if you choose this approach.