Auto-Commit Your Orgmode Files with Just and fswatch on macOS

2 min read

For a while I have used gitwatch to autocommit my Orgmode and Denote files to my private repo but I always manually started gitwatch which means I often forget to start it. I tried to setup a launch agent for it but that fails because of issues with fswatch spawning multiple gitwatch processes which inevitably causes git lock issues. What I ended up doing is using just and fswatch from a launch agent, bypassing gitwatch.

Setup Just and fswatch on macOS

If you want to automatically commit and push changes to a git repository whenever files change (great for any text-based workflow), here’s a simple setup using Just and fswatch instead of gitwatch.

Prerequisites

brew install fswatch just

Step 1: Add a Just Recipe

Add this recipe to your justfile:

# Auto-commit and push files on change
watch-org:
    #!/bin/bash
    cd /path/to/your/repo
    /opt/homebrew/bin/fswatch -0 --recursive \
        --exclude '\.git/' \
        --exclude '\.swp$' \
        --exclude '~$' \
        . | while read -d "" event; do
        git add -A
        if ! git diff-index --quiet HEAD 2>/dev/null; then
            git commit -m "Auto-commit: $(date '+%Y-%m-%d %H:%M:%S')"
            git push origin main
        fi
    done

Important: Use the full path to fswatch (/opt/homebrew/bin/fswatch) - launchd services don’t have your shell’s PATH.

Test it manually first:

just watch-org

Make a change to a file, and you should see it auto-commit. Press Ctrl+C to stop.

Step 2: Create a Launch Agent

Create ~/Library/LaunchAgents/com.autocommit.org.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.autocommit.org</string>
    
    <key>ProgramArguments</key>
    <array>
        <string>/opt/homebrew/bin/just</string>
        <string>--justfile</string>
        <string>/path/to/your/justfile</string>
        <string>watch-org</string>
    </array>
    
    <key>RunAtLoad</key>
    <true/>
    
    <key>KeepAlive</key>
    <true/>
    
    <key>StandardOutPath</key>
    <string>/tmp/autocommit.log</string>
    
    <key>StandardErrorPath</key>
    <string>/tmp/autocommit.err</string>
</dict>
</plist>

Update the paths:

  • Path to just (find with which just)
  • Path to your justfile
  • Recipe name (watch-org or whatever you called it)

Step 3: Load the Service

# Load the service
launchctl load ~/Library/LaunchAgents/com.autocommit.org.plist

# Verify it's running
launchctl list | grep autocommit
ps aux | grep fswatch | grep -v grep

Make a change to a file in your repo and check:

git log -1

You should see your auto-commit!

Managing the Service

# Stop
launchctl unload ~/Library/LaunchAgents/com.autocommit.org.plist

# Start
launchctl load ~/Library/LaunchAgents/com.autocommit.org.plist

# Check logs
tail -f /tmp/autocommit.err

Why This Beats gitwatch

  • No multiple process spawning issues - fswatch is lightweight and stable
  • No regex exclusion bugs - fswatch handles .git/ exclusions properly on macOS
  • Easy to customize - your logic lives in a simple justfile recipe
  • One process - clean and efficient

The service will automatically start on boot and keep your repo in sync!

Comments from Mastodon

3 comments • Join the discussion

Christian Tietze's avatar

@greg I never tried `just`; wouldn't a bash script without the 'watch-org' just target ceremony be more direct? Ensure fswatch is installed when running via brew without updating, and then do the actual thing directly?

Greg Newman's avatar
Greg Newman @greg
View on Mastodon

@ctietze yes but I tend to use just these days in all my projects and it helps me keep recipes documented and organized in one file (globally and project level). Give just a try in some of your projects.

Linus's avatar
View on Mastodon

@greg I do the commit in an idle timer created in a save hook set up in .dir-locals.el.The idle wait is mostly to get multiple files in the same commit.

This works as long as I don't exit emacs immediately after saving.