Optimal Data Engine
Everybody talks a different language When we decided to start building ODE we knew a few things already. One of those things was that most of our customers already had data warehousing technology. They had already…
Read moreArticle
@durdn from Atlassian wrote an excellent tutorial on how to manage dotfiles using a bare Git repository . Your dotfile setup will be ship-shape within minutes!
Yet, it took me hours to demystify the tutorial. It uses a bare repository in a novel way, by configuring $HOME as the work tree.[1]
The secret sauce is the work tree configuration, not the bare repository. I discovered that using a non-bare repository is functionally identical. This post will meticulously explain the setup, and provide a non-bare repo alternative.
$HOMEgit add and commit your dotfiles to the Git repository. The dotfiles remain at their original paths.Replicate your dotfiles by cloning down the repo, configuring it, and checking out the files. The files are checked out at their original paths relative to $HOME.
You get all the benefits of version control, are freed of the burden of maintaining symlinks, and can toss out complicated tools. Genius!
Here’s the setup all together for reference, followed by a line by line breakdown.
Note that the only two differences between using a bare and non-bare repository are:
--bare flag when initialising and cloning the repo$HOME/.cfg/ for bare and $HOME/.cfg/.git/ for non-bare.git init $HOME/.cfgalias config='/usr/bin/git --git-dir=$HOME/.cfg/.git/ --work-tree=$HOME'echo "alias config='/usr/bin/git --git-dir=$HOME/.cfg/.git/ --work-tree=$HOME'" >> $HOME/.zsh/aliasesconfig config --local status.showUntrackedFiles noconfig add .vimrc + config commit -m "add .vimrc" + Set up a remote repository on GitHub or your Git server of choice + config pushecho ".cfg" >> .gitignoregit clone <remote-git-repo-url> $HOME/.cfgalias config='/usr/bin/git --git-dir=$HOME/.cfg/.git --work-tree=$HOME'config config --local status.showUntrackedFiles noconfig checkout
Using a bare repository like @durdn’s tutorial
1. git init --bare $HOME/.cfg
2. alias config='/usr/bin/git --git-dir=$HOME/.cfg/ --work-tree=$HOME'
3. echo "alias config='/usr/bin/git --git-dir=$HOME/.cfg/ --work-tree=$HOME'" >> $HOME/.zsh/aliases
4. config config --local status.showUntrackedFiles no
5. config add .vimrc + config commit -m "add .vimrc" + set up a remote repository on GitHub or your Git server of choice + config push
Installing:
1. echo ".cfg" >> .gitignore
2. git clone --bare <remote-git-repo-url> $HOME/.cfg
3. alias config='/usr/bin/git --git-dir=$HOME/.cfg/ --work-tree=$HOME'
4. config config --local status.showUntrackedFiles no
5. config checkout
Now for the line by line breakdown. But first . . .
Two concepts should be crystal clear: the Git repository and the work tree. Skip ahead if you’re already comfortable with these.
I like this definition of repository aka repo [2]:
“Strictly speaking, a Git repository includes those objects that describe the state of the repository. These objects may exist in any directory, but typically exist in the .git directory in the top-level directory of the workspace”
“State of the repository” includes information like names of all the existing branches, the commit history on those branches, and the git log, i.e everything that Git needs for version control.
The work tree (aka working tree, working directory) does not store any information about the state of the repository. The work tree is a representation of the actual files tracked by the repository. These files are pulled out of the compressed database in the Git directory and placed on disk for you to use or modify.
A work tree is not part of the repository, and a repository doesn’t require a work tree. I find it helpful to think of the work tree as a feature of a repository.
The actual project files you get when you clone a repository are a working copy created by checking out a ref (a branch or tag or commit) – usually main or master.
If the repository and work tree are still vague concepts for you, I encourage you to do further research. Getting a solid understanding of these is essential for following the rest of this post.
git init --bare $HOME/.cfgLine 1 creates the folder .cfg, a bare Git repository which will be used to track our dotfiles. Repositories come in two flavours, non-bare (the default) and bare. Here’s a summary of key differences:
| Repository type | Initialisation | Initialised with a work tree? | Location of Git files |
| Non-bare | git init – (it’s the default) | Yes, at the top level of the project directory | In a .git folder inside the project directory /.git |
| bare | git init –bare | no | At the top level of the project directory |
Here’s their respective tree structures:


alias config='/usr/bin/git --git-dir=$HOME/.cfg/ --work-tree=$HOME'Normally, you run Git commands inside a project folder. Git commands are processed in the project’s /.git directory , and Git assumes that the working tree is located at <project>. None of this has to be configured, it’s the default behaviour of a non-bare repository.
Line 2 creates an alias named config which allows you to send git commands to the .cfg repository from any location, even outside of the repository.
It also configures the initially bare .cfg repository to set $HOME as the work tree, and store the Git state at .cfg
echo "alias config='/usr/bin/git --git-dir=$HOME/.cfg/ --work-tree=$HOME'" >> $HOME/.zsh/aliasesMakes the config alias permanently available, so that you don’t have to run line 2 every time you start a new shell session. You can either manually paste this alias or use line 3 for convenience, replacing .zsh/aliases with your chosen alias file.
If you use the zsh setup, your .zshrc file must load in the alias file with source $HOME/.zsh/aliases.
There are many ways to set up permanent aliases, e.g by adding then to your ~/.bashrc.
config config --local status.showUntrackedFiles noBefore running line 4, try typing config status, from any directory.
Our config alias is sending a Git command to the .cfg Git directory. The status will show a long list of all the untracked files under our work tree $HOME .
But .cfg should only keep track of the dotfiles that we explicitly add, not every single item on our computer. So, Line 4 sets a local configuration in .cfg to ignore untracked files.
After running line 4, run config status again, and you’ll get the message On branch master nothing to commit
Now you can add and commit dotfiles from any directory. Use your config alias combined with Git commands, like so:

Set up a remote repository on GitHub or your Git server of choice, and push as normal
config remote add origin <remote-url>
config push -u origin master
echo ".cfg" >> .gitignoreThere could be weird behaviour if .cfg tries to track itself. Avoid recursive issues by adding .cfg to your global Git ignore.
git clone <remote-git-repo-url> $HOME/.cfgAdd a --bare flag if you wish you use a bare repo
alias config='/usr/bin/git --git-dir=<path to .cfg’s Git directory> --work-tree=$HOME'Set up an alias to send Git commands to .cfg, and also set $HOME as the work tree, while storing the Git state at .cfg
For a bare repo, the path to the Git directory is at the top level of the project: $HOME/.cfg/
For a non-bare default repo, the path to the Git directory is inside a .git subdirectory: $HOME/.cfg/.git
config config --local status.showUntrackedFiles noSet a local configuration in .cfg to ignore untracked files.
config checkoutCheckout the actual content from your .cfg repository to $HOME. Git pulls the tracked files out of the compressed database in the Git directory and places them in the work tree.
E.g if you added $HOME/.zsh/aliases to .cfg, that file will be instantiated at that path on your new computer. Ta Da!
config checkout might fail with a message like:

Files on your computer might have identical locations and names to files in the .cfg repo. Git doesn’t want to overwrite your local files.
Back up the files if they’re useful, delete them if they aren’t.
Run config checkout again until you don’t get any errors.
Your dotfile setup is complete!
Treat your dotfile management system is like any other Git project. Just use the config alias to add, commit, push and pull.
Git is an easy, robust solution for dotfile management. Setting the worktree to $HOME is a brilliant idea.
I hope that this post deepens your understanding of Git, and saves someone going down a rabbit hole of researching bare repos!
Many thanks to @durdn for sharing this technique, his tutorial is the basis of this post.
Footnotes
1. Keep this in mind when researching bare repos – They are typically used as central repositories. This is not relevant for the purposes of explaining this tutorial.
“The --bare flag creates a repository that doesn’t have a working directory, making it impossible to edit files and commit changes in that repository. You would create a bare repository to git push and git pull from, but never directly commit to it.”
https://www.atlassian.com/git/tutorials/setting-up-a-repository/git-init
This explanation can be confusing, because in @durdn’s tutorial we add a working directory to the initially bare repository, which allows us to make direct commits.
2. Broader definitions work in different contexts. In common dev parlance, we talk about “pushing up your work to the repo”. Here, “repo” refers to the main remote repository. Repo commonly refers to all the code controlled by Git, i.e the whole project folder including the work tree.
Everybody talks a different language When we decided to start building ODE we knew a few things already. One of those things was that most of our customers already had data warehousing technology. They had already…
Read more
The Ensemble Logical Model is an enterprise-wide business model which, in an agile way, maps the business concepts within a given organization into an agile and adaptable model. – Remco Broekmans, LLC Author of ‘from Stories to Solutions’…
Read moreSquareweave is now Ackama.
We've merged with New Zealand company Ackama!
We're excited to be working with our Kiwi colleagues to deliver ambitious, purposeful digital products on both sides of the Tasman.
Common Code is now part of Ackama.
We’re now part of Ackama, delivering purposeful technology across the Asia-Pacific.
Together, we’re creating impact across energy, government, international development, and beyond. Delivering pragmatic, innovative solutions where they matter most.