2024-01-06, updated on 2024-08-28
tl;dr Git and GNU Stow since 2015. Feel free to skip to the details because I also hate recipe sites...
The way I manage my dot files is not particularly novel these days, but it has
worked well for me (since 2015 according to git blame
) so thought it might be
worth sharing.
I use Linux and Macs a lot, with a bit of Windows sprinkled on the side, but this process works on all 3 systems.
According to the README in my dot files git repository (spoiler alert), I was inspired by Xero on the Arch BBS.
I started using Linux in 2007 which is easy to remember thanks to my first distribution being Ubuntu Gutsy Gibbon, otherwise known as 7.10 (2007, October). Between then and my final setup were various combinations of manually backed up folders and increasingly convoluted shell scripts to manage them. Of course, I had much less need to manage them in those days when I didn't have numerous computers on which I'd want them. Unfortunately I hadn't started using Git to manage my dot files at this point, so I don't have a copy of any of these scripts. The gist was that they would look through all the files in my dot files folder, symlinking them in the correct places. Not unlike another tool might do...
Back in those days, I probably spent more time on these (and other) scripts to manage stuff rather than using the stuff that was being managed. Writing the scripts was fun though, and I learned a lot through doing that and other similar "time wasting" exercises (time wasted having fun isn't wasted!). Eventually however I decided that I'd like some more free time, and I was no longer getting much out of writing these scripts, so I looked for alternatives, which brings me to Stow.
My dotfile set up is quite simple (it's not public though unfortunately - I'm too paranoid about accidentally committing an API key or some private configuration - if I've mentioned something of interest though or if something doesn't make sense, please send me an email and ask - the worst I can do is say no!)
I have a repository imaginitively called dotfiles
stored on BitBucket
(because back in the day, that was the choice for free, private repositories)
and now, inertia GitLab. I'll typically clone this to ~/.dotfiles
as it's
still easy to cd
to, but most of the time in a file manager, I don't want to
see it. The layout of that repository looks like this:
% tree ~/.dotfiles
~/.dotfiles
├── bash
├── common-lisp
├── emacs
├── emacs-minimal
├── git
├── psql
├── README.md
├── scripts
├── shell
├── ssh
├── tmux
├── TODO.md
├── vim
└── zsh
XX directories, YY files
I've omitted a bunch of directories, but you get the idea. The names of these folders are just for organisation - they don't have any impact on the output.
The way that the stow
command works by default is to symlink everything in a
given directory into the directory above the one that stow is run from. That's
probably clearer with an example.
% tree -a ~/.dotfiles/emacs
/home/nathan/.dotfiles/emacs
└── .emacs.d
├── auto-save-list
├── backups
│ └── ...
├── customise.el
├── elpa
│ └── ...
├── .gitignore
├── init.el
└── ...
XX directories, YY files
% cd ~/.dotfiles
% stow emacs
% ls -la ~/
...
lrwxrwxrwx 1 me me 6 Jan 2 2024 .emacs.d -> .dotfiles/emacs/.emacs.d
...
One interesting point to note is that if the ~/.emacs.d
directory already
existed, stow
would still work, but it would instead symlink all the files and
directories within ~/.dotfiles/emacs/.emacs.d
rather than the ~/.emacs.d
directory itself. If a file to be stowed already exists where it would be
symlinked however, the stow
would fail, so you should never end up clobbering
anything. This is usually not what I want, so it's always worth checking what
stow
has done after running it. An example probably makes this clearer.
% # Create the files that "already exist"
% mkdir ~/.foo
% touch ~/.foo/bar
% # Create our new dotfiles
% mkdir -p ~/.dotfiles/foo-test/.foo
% touch ~/.dotfiles/foo-test/.foo/baz
% # Stow them
% cd ~/.dotfiles
% stow foo-test
% tree ~/.foo
~/.foo
├── bar
└── baz -> ../.dotfiles/foo-test/.foo/baz
0 directories, 2 files
As you can see, the already-present .foo
directory has been left intact, as
has the bar
file, and baz
has been added to ~/.foo
as a symlink to the
file in our dotfiles directory. Though this will work for now, if foo
was an
application which can create other files that we'd ultimately want tracked in
version control, a git status
inside ~/.dotfiles
would not show any new
files (because the files were symlinked and not the directory), so it would be
easy to miss them. Symlinking the directory means new files will result in a
dirty working directory, making it easy to see if you want to ignore or commit
those new files.
If inside our ~/.dotfiles/foo-test/.foo
we had created a file called bar
instead of baz
, running stow foo-test
would have failed as follows due to
bar
already existing in the target.
% stow foo-test
WARNING! stowing foo-test would cause conflicts:
* existing target is neither a link nor a directory: .foo/bar
All operations aborted.
Finally, if you want to "unstow" something, you can use the -D
flag.
% ls -l ~/.emacs.d
...
lrwxrwxrwx 1 me me 6 Jan 2 2024 ~/.emacs.d -> .dotfiles/emacs/.emacs.d
...
% cd ~/.dotfiles
% stow -D emacs
% ls -l ~/.emacs.d
ls: cannot access '~/.emacs.d': No such file or directory
And that's pretty much it! It can occasionally be a little annoying for some
directories (such as .emacs.d
) where a lot of temporary files get stored, as
you'll be adding a lot to your .gitignore
, but after the initial phase it very
rarely needs touching.