Conquer your dotfiles with VCSH and MR

Are you tired of wasting time setting up your dotfiles every time you change laptop? Here I will describe a simple method based on vcsh and later show how you can make it more customizable and modular, thanks to myrepos.

TL;DR

To manage my dotfiles I use a bare git repository, instead of symlinks, and vcsh, a small utility that makes the whole process a lot easier. On a new machine, I just need to:

$ vcsh clone https://github.com/tyrion/dotfiles dotfiles

And I am done.

Recently I switched to a more advanced and modular setup, combining vcsh with myrepos. I keep separate branches for different applications and I can run custom scripts on checkout/update. On a new machine it looks like this:

  1. Get the myrepos configuration

    $ vcsh clone keybase://private/tyrion/home mr
  2. List available application configurations

    $ cd .config/mr
    $ ls available.d/
    bash.vcsh  i3.vcsh  mr.vcsh  nvim.vcsh  pyenv.vcsh
  3. Activate the ones I want on this machine

    $ ln -s ../available.d/nvim.vcsh config.d/
  4. Get everything in the right place

    $ mr checkout

Keep reading if you want to know the details, or skip to the sauce or directly check the final result on Github!

Contents

  1. The problem
  2. Possible approaches

    1. $HOME as a git repository
    2. Separate repository plus symlinks
    3. Bare git repository
  3. VCSH to the rescue
  4. Advanced setup with myrepos

    1. Quickstart
    2. Realistic example
    3. Putting it all together

The problem

Many programs on GNU/Linux used to store their configuration inside our $HOME directory using one or more dotfiles (i.e. files starting with a dot, like .vimrc). Nowadays those files usually do not start with a dot and reside in ~/.config, but the name stuck.

Some people spend a great deal of effort in configuring and customizing their dotfiles (Have a look at reddit for some inspiration). Therefore, when switching to a new machine, or reinstalling our OS, it is very important to be able to restore all those configuration files easily.

Possible approaches

Here I will list the most common approaches to the management of dotfiles. I am going to assume that the best way to deal with the versioning of dotfiles is to use git.

$HOME as a git repository

The first solution that might come to our mind is to make our entire $HOME a git repository. This may seem a simple and good solution, although we have to be careful with sub-repositories and most importantly it is not going to be straightforward to clone our repository on a new machine (since the home folder probably already exists).

Since making our home folder a git repository seems to have some drawbacks, one reasonable way to fix the previous approach might be to put the git repository with our dotfiles somewhere else (for example, ~/.config/dotfiles ) and then symlink what we need inside our $HOME.

This in practice, is a good approach. The only downside is that on a new machine we need to manually symlink everything in the right place and it might be a bit tedious.

For this reason, when using this method it is probably a good idea to use one of the many tools available. For example see: Using GNU Stow to manage your dotfiles.

Bare git repositories

If you do not like dealing with symlinks or having to use yet an other tool to manage your dotfiles, this method is for you.

A bare git repository allows us to keep the working tree and the .git directory in different places.

$ DOT_DIR=~/.config/dotfiles
$ git init --bare $DOT_DIR
$ alias dot="`which git` --git-dir=$DOT_DIR --work-tree=$HOME"

Then we can use the dot alias to manage our dotfiles:

$ dot add .bashrc
$ dot commit -m "Add .bashrc"
$ dot push

This is very similar to our first method, except that the .git folder is not placed directly inside $HOME but resides in ~/.config/dotfiles. This means that (A) we avoid all possible problems with nested repositories and (B) we do not need symlinks!

Unfortunately, like in our first method, it is not straightforward to clone our repository when the home folder already exists. Moreover, whenever I need to setup a new machine, I always forget some of the git commands and flags required to use this method.

VCSH to the rescue

Using a bash alias like shown above, proves that we can manage our dotfiles in a satisfying way, only with git. However, I prefer using one of the many available tools that wrap git. In particular vcsh has the following advantages:

  • Easier CLI. no need to remember complex git commands or define aliases on a new machine.
  • Better UX. no problems to clone our dotfiles when the $HOME folder already exists or is non-empty.
  • Seeamless support for multiple repositories. no need to define one alias for each repository.

vcsh is licensed under GPLv2 or later and is available on most GNU/Linux distributions and OSX.

To get started with vcsh, you can create a new git repository for your dotfiles:

$ vcsh init dotfiles

Now you can run git commands, either using vcsh <repo> <git cmd> or by first activating your repository and then using git normally:

$ vcsh enter dotfiles
(master) $ git add .bashrc .vimrc
(master) $ git commit -m "initial files"
(master) $ exit
$

To get your dotfiles on a new machine, you can use vcsh clone:

$ vcsh clone https://github.com/tyrion/dotfiles dotfiles

It is also worth noting that vcsh is capable of handling multiple repositories. This means you could separate the dotfiles of different applications. For example:

$ vcsh init bash
$ vcsh bash add ~/.bashrc ~/.profile
$ vcsh init vim
$ vcsh vim add ~/.vimrc ~/.vim

For more information look at the README and try man vcsh.

Advanced setup with myrepos

If you are a true power user and you are still not completely satisfied by the above methods, here I will present a more advanced setup based on myrepos.

myrepos is a tool to manage multiple version control repositories, which works very well with vcsh, licensed under GPLv2 or later and available on most GNU/Linux distributions and OSX.

The method presented in this section is not necessarily the best for everyone. Mainly because it is more complex to setup than plain vcsh (even though you need to do this setup only once). Therefore, I encourage you to evaluate first what are you needs and then choose the most appropriate method.

The reasons that personally convinced me to adopt myrepos are the following:

  • I can write small executable scripts that run on checkout/update.
  • I can easily checkout some repositories in a different location (i.e. pyenv goes inside ~/.local/share and not ~).

NOTE: myrepos has other features, like the ability to manage almost any kind of version control system (e.g. svn, bzr), which I did not mention here, because they are not relevant for my use case. I invite you to check out the documentation.

Quickstart

To manage our repositories with myrepos, we need to edit .mrconfig files. Here is an example:

[src/mr]
# We can use almost any version control system
checkout = git clone git://git.kitenet.net/mr

[books/LFS]
# Really, also SVN ...
# It was not easy to find a working SVN repo
checkout = svn co svn://svn.linuxfromscratch.org/LFS/trunk/BOOK/ LFS

Each section represents a repository and the header specifies its location on our machine, relative to the .mrconfig file.

If we place the above .mrconfig inside any directory and run mr checkout, myrepos will fetch the repositories for us.

$ cd ~/test && ls
.mrconfig
$ mr checkout
$ tree -L 2
.
├── books
│   └── LFS
└── src
    └── mr

4 directories, 0 files

If we have many repositories, we can use the --jobs option, to make the checkouts run in parallel.

Realistic example

Here is a more realistic example of .mrconfig, showcasing the support for:

  • environment variables in the header,
  • advanced checkout with vcsh,
  • and post_update hooks.
[$HOME/.config/vcsh/repo.d/nvim.git]
checkout =
  vcsh clone https://github.com/example/nvim nvim

post_update =
  set -e
  curl -fLo ~/.local/share/nvim/site/autoload/plug.vim \
  --create-dirs https://raw.githubusercontent.com/\
  junegunn/vim-plug/master/plug.vim

  pip install -U pip
  pip install -U pynvim black mypy jedi

[$HOME/.config/vcsh/repo.d/pyenv.git]
order = 30
checkout =
  VCSH_BASE="$HOME/.local/share/pyenv" vcsh clone \
    'https://github.com/pyenv/pyenv.git' 'pyenv'

[$HOME/.local/share/pyenv/plugins/pyenv-virtualenv]
order = 30
checkout =
  git clone https://github.com/pyenv/pyenv-virtualenv.git $MR_REPO

To learn more about myrepos you can consult its website and read the manual, either online or with man mr. In the git repo you can also find an example .mrconfig.

Putting it all together

In this last section, I will show how to put together everything we have learned so far, by illustrating my current setup.

The goal is to create a single git repository, with different branches containing the dotfiles for different applications. For extra security, I recommend to use the encrypted git repositories provided by Keybase. They are especially useful if you plan to store sensitive informations, like passwords or ssh keys.

As a first step we should clone the mr-template repository, which I prepared to make things easier. In case you plan to use Github, I suggest you to fork it and use your fork instead.

$ git clone https://github.com/tyrion/mr-template.git
$ cd mr-template
$ git branch
* master
  mr

As you can see the repository contains two branches. The master branch contains only the README and can be used to store any file we do not want to be checked out automatically in our $HOME.

The mr branch is the most important and contains the configuration for myrepos and the repository definitions for all the other apps. All the other branches will be used to store the dotfiles for our applications.

Let us examine the contents of the mr branch:

$ git checkout mr
$ tree -aI .git
.
├── .config
│   └── mr
│       ├── available.d
│       │   └── mr.vcsh
│       └── config.d
│           └── mr.vcsh -> ../available.d/mr.vcsh
└── .mrconfig

4 directories, 3 files

The contents of this branch can be summarized as follows:

  • .mrconfig: the entrypoint configuration file for mr,
  • .config/mr/available.d/: repository definitions for our applications,
  • .config/mr/config.d/: repository definitions active in the current system. Contains symlinks to available.d/.

The .mrconfig is used only to include everything inside config.d/. Its content should be:

[DEFAULT]
include = cat ~/.config/mr/config.d/*

Therefore, to add the dotfiles for a specific application to our system, we have to enable its repository definition (placed inside available.d/) by creating a symlink inside config.d/.

This mechanism allows us to only activate the dotfiles for the applications that we need on the current machine, while keeping all the repository definitions inside the same git repository.

Notice that mr.vcsh is symlinked inside config.d/ and thus is enabled by default. Let us check its contents:

[$HOME/.config/vcsh/repo.d/mr.git]
order = 0
# NOTE: put here the correct url for your repository
checkout =
  vcsh clone -b mr https://github.com/tyrion/mr-template.git mr

Remember to edit the url to point to your dotfiles repository and commit it.

As an example, let us now add a simple .bashrc to our repository. The first thing to do is to add the repository definition. Place the following inside .config/mr/available.d/bash.vcsh:

[$HOME/.config/vcsh/repo.d/bash.git]
order = 10
# NOTE: put the correct url for your repository
checkout =
  vcsh clone -b bash https://github.com/tyrion/mr-template.git bash

This instructs vcsh to checkout the bash branch of our repository. Let us create this branch, since it does not exist, and add our dotfiles to it:

$ git checkout -b bash init
Switched to a new branch 'bash'
$ echo "echo 'Hello o/'" > .bashrc
$ git add .bashrc
$ git commit -m "Add dummy .bashrc"

You should also create a symbolic link inside .config.d/ if you want your bash branch to be enabled by default.

NOTE: It is very important to create your branches from the init tag, to ensure they will start from a clean/empty state.

That's it. Assuming you pushed your branches to your remote repository, you are now ready to check out your dotfiles on any machine:

$ # NOTE: always put your repo
$ vcsh clone -b mr https://github.com/tyrion/mr-template.git mr

Enable/disable the branches that you want and run mr checkout:

$ cd .config/mr
$ ln -s ../available.d/bash.vcsh config.d/
$ mr checkout

All your dotfiles should be in the right place. Congratulations 🎉