Part one of a three part series about how I use a git repo to manage my configuration across MacOS, Windows and Linux (WSL 2 for now!)

I like using git. I use it every day for work, and applying it to different contexts helps me gain a better understanding of how git works overall and has a multiplicative effect on my abilities as a developer. So when I started needing to keep my configuration files (dotfiles from here on out) across multiple computers (for this post, just my work and home laptops), git felt like the natural choice for managing this. However, it’s ideal for these files tend to need to live in very specific places (ie. .vimrc should be in $HOME ) since I also don’t want to over customize (believe it or not!) Due to this and the fact that I prefer to avoid symlinks, I opted to setup a bare git repo which doesn’t have a working tree (an alias defined below passes work-tree as whatever $HOME is. I’ve essentially followed this amazing tutorial.

git init --bare $HOME/.cfg
alias config='/usr/bin/git --git-dir=$HOME/.cfg/ --work-tree=$HOME'
config config --local status.showUntrackedFiles no
echo "alias config='/usr/bin/git --git-dir=$HOME/.cfg/ --work-tree=$HOME'" >> $HOME/.bashrc

Now that I have all the files I want version controlled and pushed to a remote repository, this is how I would do an install on a fresh computer:

git clone --bare <repo URL> $HOME/.cfg # A classic git clone, like any old repo
    alias config='/usr/bin/git --git-dir=$HOME/.cfg/ --work-tree=$HOME' # Define the alias in the current shell scope 
    config checkout # to give the files a $HOME
    # I can also use config as if it were the git command when I want to add, commit and push changes
    ./.install.sh  # to install dependencies (more on that below)
    Open neovim and run :PlugInstall # to install neovim plugins

This is where I chose to sprinkle in some simple bash for installing dependancies, essentially to save me some time during setup. Cyber salt bae gif Once I have brew installed, I can just run this script to do the needful. It’s very straightforward:

# !/bin/sh
    command -v brew
    if [[ $? != 0 ]] ; then
            echo "Please install homebrew!"
    else
            brew install neovim
            curl -fLo ~/.vim/autoload/plug.vim --create-dirs https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim && echo "Vim plug installed"
            brew install zsh
            brew install ripgrep
            brew install fzf # et cetera for whatever else you want to install
    fi

Both computers are set up, and now all I have to do to keep things up to date is run git pull every morning when I log in…oh I have to remember to do that. Every. Morning.

Reader, mornings can have wildly different starts for me depending on how well I slept the night before, how the weather was on my morning dog walk, and how the commute into the office went. I did not remember to git pull every morning; the result was some merge conflicts and getting annoyed with bugs I’d thought I’d fixed (and ultimately had.) So, I opted to automate this using iTerm scripting (since already use iTerm as my terminal). Basically I use this shell script that I wrapped in Applescript:

do shell script "
    /usr/bin/git --git-dir=/path/to/.cfg/ fetch
    UPSTREAM=${1:-'@{u}'}
    LOCAL=$(/usr/bin/git --git-dir=/path/to/.cfg/ rev-parse @)
    REMOTE=$(/usr/bin/git --git-dir=/path/to/.cfg/ rev-parse \"$UPSTREAM\")
    BASE=$(/usr/bin/git --git-dir=/path/to/.cfg/ merge-base @ \"$UPSTREAM\")
    
    if [ $LOCAL = $REMOTE ]; then
      osascript -e 'tell app \"System Events\" to display dialog \"Config files are up-to-date!\"' 
    elif [ $LOCAL = $BASE ]; then
      osascript -e 'tell app \"System Events\" to display dialog \"Hold tight, updating config files!\"' &&
    	/usr/bin/git --git-dir=/path/to/.cfg/ --work-tree=/path/to pull origin release
    elif [ $REMOTE = $BASE ]; then
      osascript -e 'tell app \"System Events\" to display dialog \"Local config files have changes that need to be pushed!\"' 
    else
      osascript -e 'tell app \"System Events\" to display dialog \"Config files have diverged, might wanna sort that out.\"' 
    fi
    "

Sidebar: I know that this method of scripting is going to be deprecated eventually, but I’ll cross that bridge and port over to the new python api at a later time

Finally, there are definitely things that I still want to keep special for each computer. For my work computer there are specific git settings I use — I keep those in a non-version controlled git config file named .gitconfig.work and then in my version controlled .gitconfig I include it like so:

[include]
            path = .gitconfig.work

There you have it, a more general but hopefully not dull overview of my configuration management across computers of the same OS. This is an approach that has evolved over the course of two years or so now, and will continue to, and I hope others might find it useful!

This post is also available on DEV.