Dotfile management on MacOS with Git
Tags: productivity, workflow, git
Published: 2019-12-06 01:07:26

---
series: How I Manage My Dotfiles Cross Platform
title: Dotfile management on MacOS with Git
published: true
description: Part one of a three part series about how I use a git repo to manage my configuration across MacOS, Windows and Linux
tags: productivity, workflow, git
cover_image: https://thepracticaldev.s3.amazonaws.com/i/rk5nnpz79exzvdyodfx9.jpg
---

*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.](https://www.atlassian.com/git/tutorials/dotfiles)

```bash
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:
```bash
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](https://media.giphy.com/media/l0He7tLZz0qI6EpuU/source.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](https://www.iterm2.com/documentation-scripting.html) (since already use iTerm as my terminal). Basically I use this shell script that I wrapped in Applescript:
```bash
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:
```bash
[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!

Addendum:
In the event you need to link up an existing bare repo to a remote, those steps are covered in [Jon's helpful comment below](https://dev.to/jonlauridsen/comment/195m5)!