Sorry for the mess. I'm moving things around. It'll be better soon.

Managing Dotfiles with GNU Stow

13th November 2018

Managing dotfiles can be tricky when you have multiple machines. Fortunately, there’s a beautifully simple tool that makes this easy: GNU Stow. If you have dotfiles that you want to share across multiple machines, or manage revisions, GNU Stow will make it easy.

Here you’ll learn about how to use GNU Stow to manage your dotfiles. I’ll start with a simple example, explain in detail how it works, and finally demonstrate a recommended workflow for using GNU Stow to manage your own dotfiles.

What is GNU Stow?

From the website:

GNU Stow is a symlink farm manager which takes distinct packages of software and/or data located in separate directories on the filesystem, and makes them appear to be installed in the same place.

While GNU Stow is an excellent tool for package management, its real beauty lies in the simple design that makes it adaptable to other uses. Despite not being designed specifically for dotfiles, it is a perfect tool for the job.

GNU Stow is available nearly everywhere, and can probably be installed with your favourite package manager as the package stow. I use it on OpenBSD, FreeBSD, Debian, and macOS (available via MacPorts and HomeBrew).

Note that GNU Stow does not work on Windows. There exist some alternatives such as Eric Subach’s Stow Lite, but I do not have enough experience with such alternatives to comment on their suitability.

A Simple Example

Let’s get started with a simple example. You probably already have a .gitconfig in your home directory, so let’s start by stow-ing that.

Create a new home for your git-related dotfiles:

$ mkdir -p ~/dotfiles/git

Now move your .gitconfig there:

$ mv ~/.gitconfig ~/dotfiles/git

Get it back using stow:

$ cd ~/dotfiles
$ stow git

Behold, your git config:

$ cd ~
$ ls -l .gitconfig
lrwxr-xr-x  1 srbaker  srbaker  23 Jun 13 16:00 .gitconfig -> dotfiles/git/.gitconfig

If you’re not quite sure what running stow will do, you can find out by enabling verbose output, and telling it not to perform operations: stow --verbose --no and it will display what it would have done, without making any changes.

By now you have all of the knowledge necessary to manage all of your dotfiles using stow using the pattern from above. In the following sections, you’ll learn details about how it works. If you would like to get started immediately, you can just skip to the recommended workflow section at the end and learn the details later (if needed).

How It Works

The basic premise of GNU Stow is that it takes files in multiple directories, and manages symlinks to make them appear in one directory.

When invoked with a directory as an argument, stow simply changes into that directory, and creates a symlink for everything it contains to the parent directory. When invoked with many directories as arguments, it does this for each directory listed.

Take this example:

$ pwd
/home/srbaker/stow-example/stow
$ ls -R
bar foo

./bar:
barfile

./foo:
foofile

If we run stow bar, we will see that barfile appears the in the parent directory.

$ ls ..
barfile stow

This is everything you need to know to make full use of stow. By default, GNU Stow is smart enough to do the right thing with files that you probably don’t want included, handling directories, and identifying conflicts.

Ignoring Files

GNU Stow can ignore files that you don’t wish to have stow-ed. You can tell stow to ignore files on the command line using the --ignore argument, and supplying a regular expression. If we want to use stow, but ignore files named foo we could do so like so:

stow --ignore=foo *

Likewise, if we wanted to ignore all files starting with the word foo we could use a regular expression: stow --ignore=foo.* *.

It would be cumbersome to add the --ignore argument to every single run. To solve this, stow will read an ignore list from .stow-local-ignore in the current directory, as well as a global .stow-global-ignore in your home directory.

By default, when neither a local nor global ignore list exists, GNU Stow will use its default ignore list which includes entries for version control related files, emacs backup and autosave files, and README, LICENSE, and COPYING. You can look at the GNU Stow documentation for the default ignore list.

Handling Directories

When stow-ing files, directories will be handled as well. I use this trick for managing my ~/bin directory, which you will see in the recommended workflow section below.

Consider that you have a directory foo that contains a file foofile and a directory qux which itself contains a file fooquxfile, as demonstrated here:

stow-example$ find foo
foo
foo/foofile
foo/qux
foo/qux/quxfile

Running stow foo will create links to both the file foofile and the directory qux:

stow-example$ ls -l ..
total 4
lrwxrwxrwx 1 srbaker srbaker   24 Nov 13 16:49 foofile -> stow-example/foo/foofile
lrwxrwxrwx 1 srbaker srbaker   20 Nov 13 16:49 qux -> stow-example/foo/qux
drwxr-xr-x 4 srbaker srbaker 4096 Nov 13 16:42 stow-example

You can see that foofile is appropriately a link to foo/foofile and qux is a link to the directory foo/qux.

But what if in addition to foo containing a directory qux as described above, you also have a directory bar which has a qux directory of its own? From the example above, we can see that the stow-ed qux is already a symlink to foo/qux. Where will bar/qux live?

Fortuantely, stow does the right thing:

stow-example$ stow bar
stow-example$ ls -l ..
total 8
lrwxrwxrwx 1 srbaker srbaker   24 Nov 13 16:54 barfile -> stow-example/bar/barfile
lrwxrwxrwx 1 srbaker srbaker   24 Nov 13 16:54 foofile -> stow-example/foo/foofile
drwxr-xr-x 2 srbaker srbaker 4096 Nov 13 16:54 qux
drwxr-xr-x 4 srbaker srbaker 4096 Nov 13 16:42 stow-example

The qux directory that stow created is now a directory of its own:

stow-example$ ls -l ../qux
total 0
lrwxrwxrwx 1 srbaker srbaker 31 Nov 13 16:54 quxfile -> ../stow-example/foo/qux/quxfile
lrwxrwxrwx 1 srbaker srbaker 32 Nov 13 16:54 quxfile2 -> ../stow-example/bar/qux/quxfile2

When stow creates a directory and links the contents from multiple sources inside on the second run, it’s called tree folding. stow has noticed that the directory qux is in two different sources, and folds them into the same tree. GNU Stow calls the reverse of this operation tree unfolding.

Identifying Conflicts

The default beahviour of GNU Stow covers most use cases without even displaying output. In most cases, it just does the right thing. Since files are being linked around the filesystem, it’s possible that stow will be asked to put two files with the same name in the same place. Consider two source directories foo and bar which both have a file called bazfile inside:

stow-example$ stow *
WARNING! stowing foo would cause conflicts:
  * existing target is stowed to a different package: baz => stow-example/bar/baz
All operations aborted.

In this case stow has recognized that there is a conflict, and refuses to make any changes.

A stow invocation that would overwrite an existing file also results in a conflict warning that aborts all operations. Consider that our parent directory (the target) already has a file called foofile and we try to stow the foo directory containing foofile:

stow-example$ stow foo
WARNING! stowing foo would cause conflicts:
  * existing target is neither a link nor a directory: foofile
All operations aborted.

This very careful default behaviour means that running stow is always a completely safe operation: no files will be moved or overwritten unless it can be done non-desctructively.

Using GNU Stow to manage your dotfiles is made infinitely better by storing your dotfiles in version control. By doing this, you will have history of your edits, and you can use existing tooling to share your dotfiles across machines.

If you keep your dotfiles in a git repository, setting up a new machine is as easy as:

git clone my-git-host:dotfiles.git && cd dotfiles && stow *

The machine you’re sitting at right now probably already has your preferred dotfiles, so you can get started immediately.

$ git init dotfiles

Now, for each program you have dotfiles for, move them into a directory inside your dotfiles working copy. For example, if you want to stow your git and bash dotfiles, you might do the following:

$ mkdir git
$ mv .git* dotfiles/git
$ mv .bash_profile .bashrc .bash_aliases dotfiles/bash
$ cd dotfiles && stow *

Whenever you make changes to your dotfiles in place, you will need to remember to commit those changes. If you add new files, you will have to remember to re-run stow: cd ~/dotfiles && stow *. If you’ve removed files since the last run, you should re-stow: cd ~/dotfiles && stow -R *.

Finally, if you decide that you would like to un-stow all of your dotfiles for whatever reason, you can cd ~/dotfiles && stow -D.

Conclusion

I hope this introduction to dotfile management with GNU Stow has been helpful. If you find any errors, or have any questions, I am more than happy to respond to email.