Automated Syncing with Git
Syncing folders with Git Sync and Systemd

13 August 2016

I use Git quite a lot. Pretty much all of my personal projects end up in a Git repository, and are pushed to BitBucket. This doesn’t just mean software projects, but also things like my notebook (I’ve been learning org-mode in Emacs recently), and the XML generated by Gnucash that I use to keep track of my finances.

In a software dev project, Git is typically part of my process. I’ll have something I want to do in mind. I’ll make some changes. I’ll test. Then I’ll commit with a meaningful commit message.

Writing a note in my notebook doesn’t have such a disciplined structure, and I don’t care to write a commit message. I just want it to be committed and sent to BitBucket so I can pull it elsewhere. Really, I want Dropbox-style syncing, just with the version history and merging of Git.

Script it

As with many quick automations, the first step is to write a script. I started writing this script myself, after all it sounded like it would just be a case of pull, commit, push, but soon realized that it was more complicated than I thought. What if a merge conflict happens? Then the step where you add everything will break things. What if you’re in the middle of doing something manually, like resolving a merge conflict, and the script runs? Instead of writing something to handle these cases, I found that someone else already had here. In terms of setup on my Git repo, I needed to set some config flags using the following two commands.

git config --bool branch.master.sync true
git config --bool branch.master.syncNewFiles true

The syncNewFiles flag will let it add new files. It’s worth making sure that your .gitignore file covers anything that you don’t want to commit.

After that, I still wrapped my command in a shell script, to make it apply to multiple repos. When I get around to setting this up on my Windows machine, the Windows-friendly version of this script will also include running something like MinGW, since Git Sync is a Bash script.

#!/bin/sh

cd ~/doc/gnucash
~/auto/git-sync

cd ~/doc/notebook
~/auto/git-sync

Put it on a Schedule

How you schedule the script to run will depend on your system. On Linux, you’re likely to have Cron available. Windows has the Task Scheduler. Anything that lets you run your shell script at regular intervals will work.

I am running Arch Linux, which uses Systemd to manage these sorts of things internally, so I decided to give writing my own Systemd unit a go.

The first thing that you need is a service file telling Systemd how to run your script. This includes some useful settings like a description and a “Niceness”. Nicer programs have less priority when it comes to allocating resources. I would rather my script takes a bit longer and doesn’t slow down the system at all for me (not that the script is actually doing anything intense), so I made it super nice.

#Saved in ~/auto/autosync-repos.service
[Unit]
Description=Automatically does git push and pull on a number of repos

[Service]
Type=simple
ExecStart=/home/justin/auto/autosync-repos.sh
Nice=19

[Install]
WantedBy=autosync-repos.target

The next thing that Systemd needs is a timer, which is the trigger that launches the service at regular intervals. For testing, you can launch the service without the timer, or have it triggered by other events. I want my script to run every 15 minutes, starting soon after the machine boots up.

If you don’t specify which service to run, the timer will find a service with the same name as itself, just a different extension.

#Saved in ~/auto/autosync-repos.timer
[Unit]
Description=Automatically does git push and pull on a number of repos

[Timer]
OnBootSec=1min
OnUnitActiveSec=15min

[Install]
WantedBy=timers.target

The last step is to register these config files with Systemd. These commands are run from the terminal. It will create a symlink to your config files in the folders that Systemd is checking, so you can keep your custom Systemd units arranged on your drive however you want.

systemctl --user enable /home/justin/auto/autosync-repos.service
systemctl --user enable /home/justin/auto/autosync-repos.timer

I found that the path to your service files needed to be absolute.

The --user flag installs it in the Systemd config that applies to your current logged in user, not everyone that shares your computer. It also means that it runs as your user, so your script can use your ssh keys and it knows where your home directory is.

But Does it Work?

If you want to test that your script works with the service config, you can run it with

systemctl --user start autosync-repos.service

and then, after waiting a few seconds, run

systemctl --user status autosync-repos.service

to see its output. You can also use that status command to make sure that the timer has been running it.

What am I Missing?

The big thing that I’m missing is error reporting. The Git Sync script is set up that it won’t do much harm if a merge conflict comes up or my network is dead, but it will fail quietly. The Systemd docs suggest that you can set it up to email you when things go wrong. This could work, but I haven’t played with it yet. If I ever add that, it will be the topic of a future post.

Update - 7 Nov 2017

I’ve finally gotten around to having this script tell me when things go wrong. Read more here.