DEV Community

Cover image for Dotfile management on MacOS with Git
Sabrina
Sabrina

Posted on • Updated on

Dotfile management on MacOS with Git

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
    "
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (2)

Collapse
 
jonlauridsen profile image
Jon Lauridsen

Nice article, thanks, I'll give this a try. I think it'll also allow capturing both my work and home setup by using two different branches, so I don't pull in Kubernetes configuration files etc. to my home setup.

You don't explicitly mention how to link up the bare repo to a remote repo, and I just wanted to confirm to anyone who might be reading this wondering about that step that it's just the normal remote-adding workflow:

config branch -M main
config remote add origin git@github.com:<user>/<repo>.git
config push -u origin main
Enter fullscreen mode Exit fullscreen mode

Of very minor note it sounds like it's common to direct --bare repos into folders suffixed .git (reading e.g. this article), so I called mine .cfg.git. Not that it makes a technical difference either way, I just think it'll be a small reminder for my future self to remember what the folder is for.

Collapse
 
deusmxsabrina profile image
Sabrina

Thank you, super happy to hear it might be useful and I really appreciate the addition about the bare repo set up. I'll edit the article when I have a bit more time (with credit!) but wanted to make sure I said thanks now!

I have used this set up on different branches.

I appreciate the note on naming too; that makes sense and I think in hindsight I should have named mine something similar.