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.
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!
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)
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:
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.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.