Skip to main content

Using a Bare Git Repository for dotfiles

·6 mins· loading · loading
Git
Race Dorsey
Author
Race Dorsey
Table of Contents
Learn to easily manage dotfiles with Git’s bare repository feature–no symlinks, no additional tools, just git.

Intro
#

Since I switched to linux 6 months ago I have had my dotfiles backed up to my NAS. These dotfiles specify how some of my environments and tools are configured and I’ve been wanting to publish them for others to view. The problem was that I wasn’t happy with some of the approaches to achieve this until I came across this post which mentioned using a bare git repository to manage dot files. The post made it seem like a good solution so I investigated further and gave it a try.

What is a bare git repository?
#

To understand a bare git repository, first let’s look at a traditional git repository. Under a traditional git repository the .git metadata is a folder within the project folder. The rest of your code and files are then within this project folder. Here’s a basic example:

my-project/
├── .git/			# Git metadata (hidden)
│   ├── HEAD
│   ├── objects/
│   ├── refs/
│   ├── config
│   └── ...
├── src/			# ← Your project files
├── README.md		# ← Your project files
└── ...		 		# ← Your project files

Files within the my-project folder are then inherently part of git’s working tree which means git will automatically pick up on changes to files made within your project folder.

Conversely, a bare git repository instead has the git metadata at a folder’s root level, and there is no direct working tree which means your bare git repository would look something like this:

my-project.git/		# `.git` in folder name is a convention
├── HEAD			# Git metadata at root level
├── objects/
├── refs/
├── config
└── ...

Since there is no direct working tree under a bare git repository, you can instead designate a folder as the working tree. This can be useful for servers or backup situations where perhaps you want to designate a high-level folder as the working tree. There’s a bit more nuance to bare git repositories but for our purposes here this should be a sufficient background.

Dotfile options; why not symlink? #

The obvious problem with managing/sharing dotfiles is that their contents are spread throughout your system, so under a traditional repository you aren’t able to capture these files unless you use symlinks or a symlink manager like Stow. Symbolic links work by making a file appear to be in two separate locations. To make these symbolic links work best with git you need to first move the files into your traditional dotfiles repository and then symlink the files to the place your computer normally expects them.

The downside to these symlink approaches for me was added complexity. I would either be adding extra steps to make it work with each interaction, or I would be adding a layer (Stow) on top of my dotfiles and git.

When I read Marcel’s post the simplicity of the bare git repository seemed to be exactly what I was looking for. No symlinks, no additional packages, and pretty straightforward setup. I also really liked the idea of using a native git feature I had not heard of before–I had to try it.

Setting up a bare git repository for dotfiles
#

The whole set-up took me less than 5 minutes. Here are the exact steps I took, with some commentary along the way:

1. create bare git repo
#

$ DOTFILES=$HOME/.config/dotfiles.git
$ git init --bare $DOTFILES
Initialized empty Git repository in /home/rogue/.config/dotfiles.git/

The DOTFILES can be wherever you want the git folder. The .git in the name is a convention to show that it is a bare git repository.

2. create an alias to use
#

$ alias gitdf='git --git-dir=$DOTFILES --work-tree=$HOME'

Here we specify a --work-tree which for my purposes I set as $HOME. If you know you’ll want to use this for dotfiles outside of your home directory, you could also set this to your root directory.

You’ll also want to take a moment and add the alias to your shell’s rc file for future use.

Note: we’ll be using gitdf for git commands going forward.

3. update config to not show untracked files
#

$ gitdf config status.showUntrackedFiles no

You’ll want this so you dont see all the untracked files listed when using gitdf status

4. add specified directories and files
#

Use gitdf add to add any directory and file you want to be tracked by your bare git repository. If you intend to make the dotfiles available in a public git forge then make sure you avoid adding any sensitive files or directories like ~/.ssh.

Here are the files and directories I chose to start:

$ gitdf add ~/.config/hypr/
$ gitdf add ~/.config/kitty/
$ gitdf add ~/.config/waybar/
$ gitdf add ~/.bashrc

5. check status of tracked files
#

$ gitdf status
On branch main

No commits yet

Changes to be committed:
(use "git rm --cached <file>..." to unstage)
	new file:   .bashrc
	new file:   .config/hypr/hyprland.conf
	new file:   .config/hypr/hyprpaper.conf
	new file:   .config/kitty/current-theme.conf
	new file:   .config/kitty/kitty.conf
	new file:   .config/waybar/config
	new file:   .config/waybar/hypr-shared.json
	new file:   .config/waybar/power_menu.xml
	new file:   .config/waybar/scripts/toggle-audio.sh
	new file:   .config/waybar/style.css

Untracked files not listed (use -u option to show untracked files)

This will show the files you’ve added to your bare git repository, or if you’ve made any changes to a tracked file.

6. Initial commit
#

$ gitdf commit -m "initial commit"
[main (root-commit) 18b0531] initial commit
 12 files changed, 1228 insertions(+)
 create mode 100644 .bashrc
 create mode 100644 .config/hypr/hyprland.conf
 create mode 100644 .config/hypr/hyprpaper.conf
 create mode 100644 .config/kitty/current-theme.conf
 create mode 100644 .config/kitty/kitty.conf
 create mode 100644 .config/waybar/config
 create mode 100644 .config/waybar/hypr-shared.json
 create mode 100644 .config/waybar/power_menu.xml
 create mode 100755 .config/waybar/scripts/toggle-audio.sh
 create mode 100644 .config/waybar/style.css

7. set remote
#

$ gitdf remote add origin ssh://[email protected]/racehd/dotfiles.git

Just specify your own remote for wherever you intend to push the files to.

8. push
#

$ gitdf push --set-upstream origin main
Enumerating objects: 20, done.
Counting objects: 100% (20/20), done.
Delta compression using up to 32 threads
Compressing objects: 100% (19/19), done.
Writing objects: 100% (20/20), 12.52 KiB | 12.52 MiB/s, done.
Total 20 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To ssh://codeberg.org/racehd/dotfiles.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.

For this first gitdf push we do need to specify --set-upstream origin main, but going forward you’ll be able to push with just gitdf push.

9. Future changes
#

The following are all basic git commands (just using our gitdf alias). If you need a git refresher then these may be helpful in the future:

  • gitdf status to see changed files
  • gitdf diff to see the diffs of changed files
  • gitdf add -u to add all changes to tracked files
  • gitdf commit -m "update dotfiles" to make your commits
  • gitdf push to push to your remote.
  • gitdf ls-files to see a list of all tracked files
  • gitdf add ~/path/to/dir-or-file to add new tracked directories or files.
  • gitdf rm --cached ~/path/to/file if you need to remove a file from tracking but keep it locally.

Conclusion
#

That’s it! What I really liked about this was that it only uses git (via the gitdf alias) so I didn’t need to introduce any complexity stemming from using native symlinks or using a symlink manager. I can just stick with the a tool I already know.

If you’re interested, my dotfiles can be found here.

Did you enjoy this post or find it helpful?

If so, please leave a like! It doesn't go into an algorithm—it just lets me know a human reader valued it in some way (you are human, right?)