The bare minimum for syncing Git repos
I have some personal Git repos that I want to sync between my devices – my dotfiles, text expansion macros, terminal colour schemes, and so on.
For a long time, I used GitHub as my sync layer – it’s free, convenient, and I was already using it – but recently I’ve been looking at alternatives. I’m trying to reduce my dependency on cloud services, especially those based in the USA, and I don’t need most of GitHub’s features. I made these repos public, in case somebody else might find them useful, but in practice I think very few people ever looked at them.
There are plenty of GitHub-lookalikes, which are variously self-hosted or hosted outside the USA, like GitLab, Gitea, or Codeberg – but like GitHub, they all have more features than I need. I just care about keeping my files in sync. Maybe I could avoid introducing another service?
As I thought about how Git works, I thought of a much simpler way – and I’m almost embarrassed by how long it took me to figure this out.
A Git repo is just a collection of files
In Git repos, there’s a .git folder which holds the complete state of the repo. It includes the branches, the commits, and the contents of every file. If you copy that .git folder to a new location, you’d get another copy of the repo. You could copy a repo with basic utilities like cp or rsync – at least, as a one-off. I wouldn’t recommend using them for regular syncing; it would be easy to lose data, because they don’t know how to merge changes from different devices.
Git’s built-in push and pull commands are smarter: they can synchronise this state between locations, compare the history of different copies, and stitch the changes together safely. Within a repo, you can create a remote location, a pointer to another copy of the repo that lives somewhere else. When you push or pull, your local .git folder gets synchronised with that other copy.
We’ve become used to the idea that the remote location is a cloud service – but it can just as easily be a folder on your local disk – and that gives me everything I want.
Bare and non-bare repositories
Before I explain the steps, I need to explain the difference between bare and non-bare repositories.
In our day-to-day work, we use non-bare repositories. They have a “working directory” – the files you can see and edit. The .git folder lives under this directory, and stores the entire history of the repo. The working directory is a view into a particular point in that history.
By contrast, a bare repository is just the .git folder without the working directory. It’s the history without the view.
You can’t push changes to a non-bare repo – if you try, Git will reject your push. This is to avoid confusing situations where the working directory and the .git folder get out of sync. Imagine if you had the repo open in a text editor, and somebody else pushed new code to the repo – suddenly your files would no longer match the Git history.
Whenever we push, we’re normally pushing to a bare repository. Because nobody can “work” inside a bare repo, it’s always safe to receive pushes from other locations – there’s no working directory to get out of sync.
My new setup
I have a home desktop which is always running, and it’s connected to a large external drive. For each repo, there’s a bare repository on the external drive, and then all my devices have a checked-out copy that points to the path on that external drive as their remote location. The desktop connects to the drive directly; the other devices connect over SSH.
This only takes a few commands to set up:
Create a bare repository on the external drive.
$ cd /Volumes/Media/bare-repos $ git init --bare dotfilesSet the bare repository as a remote location.
On the home desktop, which mounts the external drive directly:
$ cd ~/repos/dotfiles $ git remote add origin /Volumes/Media/bare-repos/dotfilesOn a machine, which can access the drive over SSH:
$ cd ~/repos/dotfiles $ git remote add origin alexwlchan@desktop:/Volumes/Media/bare-repos/dotfilesThis allows me to run
git pushandgit pullcommands as normal, which will copy my history to the bare repository.Clone the bare repository to a new location.
When I set up a new computer:
$ git clone /Volumes/Media/bare-repos/dotfiles ~/repos/dotfiles
This approach is very flexible, and you can store your bare repository in any location that’s accessible on your local filesystem or SSH. You could use an external drive, a web server, a NAS, whatever. I’m using Tailscale to get SSH access to my repos from other devices, but any mechanism for connecting devices over SSH will do. (Disclaimer: I work at Tailscale.)
Of course, this is missing many features of GitHub and the like – there’s no web interface, no issue tracking, no collaboration – but for my small, personal repos, that’s fine. There’s also no third-party hosting, no risk of outages, no services to manage. I’m just moving files about over the filesystem. It feels like the Git equivalent of static websites, in a good way.
Reflections
I used to throw every scrap of code onto GitHub in the vague hope of “sharing knowledge”, but most of it was digital clutter.
Nobody was reading my personal repos in the hope of learning something. They’re a grab bag of assorted snippets, with only a loose definition or purpose – it’s unlikely another person would know what they could find, or spend the time to go looking. Sharing knowledge requires more than just publishing code somewhere; you need to make it possible for somebody to find.
Extracting my ideas into standalone, searchable snippets makes them dramatically more useful and discoverable. There are single blog posts that have done more good than my entire corpus of code on GitHub – and I have hundreds of blog posts.
I still have plenty of public repos, but it’s specific libraries or tools with a clear purpose. It’s more obvious whether you might want to read it, and better documented if you do. It’s an intentional selection, not a random set of things I want to keep in sync.
For years, I’ve been using a social media site as a glorified file-syncing service, but I don’t need pull requests, an issue tracker, or a CI/CD pipeline to move a few macros between my machines – just a place to put my code. As with so many digital things, files and folders are all I need.