Skip to content

Instantly share code, notes, and snippets.

@jaredmdobson
Created May 14, 2026 16:48
Show Gist options
  • Select an option

  • Save jaredmdobson/6d5ec01ec7c8659cf0cbc9c0c79c1602 to your computer and use it in GitHub Desktop.

Select an option

Save jaredmdobson/6d5ec01ec7c8659cf0cbc9c0c79c1602 to your computer and use it in GitHub Desktop.
Auto-pull every git repo in ~/Developer on macOS (gitup + launchd, 30-min interval)

Auto-pull every git repo in ~/Developer on macOS

Keep all your local clones up to date in the background, so you never cd into a stale checkout. Uses gitup + a launchd agent that fires every 30 minutes.

gitup is "safe by default" — it skips dirty working trees, diverged branches, and detached HEADs instead of clobbering them. Combine it with the discipline of only editing inside a git worktree and your main checkouts stay clean mirrors of origin.


1. Prereqs

You need uv (one-liner installer):

curl -LsSf https://astral.sh/uv/install.sh | sh

2. Install gitup

uv tool install gitup

This drops the gitup binary in ~/.local/bin (make sure that's on your PATHuv tool install will warn you and show the line to add to .zshrc if it isn't).

3. Register your repos

gitup --add ~/Developer/*

Glob expands to every top-level directory; gitup only bookmarks the ones that are actually git repos. Re-run any time you clone new stuff (or let the LaunchAgent below do it for you — it re-adds on every run).

Verify:

gitup --list

Dry-run pull once to confirm it works:

gitup

4. Run it every 30 minutes via launchd

Two gotchas this avoids:

  • launchd does not expand $HOME (or anything else) inside plist strings — so we interpolate at heredoc write-time.
  • zsh -c runs as a non-interactive shell, which does not source .zshrc. uv tool install adds its PATH line to .zshrc, so launchd wouldn't find gitup on its own. We resolve the absolute path with $(command -v gitup) at write-time and bake it in.
mkdir -p ~/Library/LaunchAgents ~/Library/Logs
GITUP=$(command -v gitup)
test -x "$GITUP" || { echo "gitup not on PATH — fix that first"; exit 1; }
cat > ~/Library/LaunchAgents/com.user.gitup.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.user.gitup</string>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/zsh</string>
        <string>-c</string>
        <string>$GITUP --add \$HOME/Developer/* 2>/dev/null; $GITUP</string>
    </array>
    <key>StartInterval</key>
    <integer>1800</integer>
    <key>RunAtLoad</key>
    <true/>
    <key>StandardOutPath</key>
    <string>$HOME/Library/Logs/gitup.log</string>
    <key>StandardErrorPath</key>
    <string>$HOME/Library/Logs/gitup.log</string>
</dict>
</plist>
EOF

Load it:

launchctl unload ~/Library/LaunchAgents/com.user.gitup.plist 2>/dev/null
launchctl load   ~/Library/LaunchAgents/com.user.gitup.plist

RunAtLoad triggers an immediate first run; after that, every 30 minutes (1800 seconds). Tail the log to confirm:

tail -f ~/Library/Logs/gitup.log

5. Useful management commands

# pause auto-pull
launchctl unload ~/Library/LaunchAgents/com.user.gitup.plist

# resume
launchctl load ~/Library/LaunchAgents/com.user.gitup.plist

# trigger one run on demand
launchctl kickstart -k gui/$(id -u)/com.user.gitup

# is it loaded?
launchctl list | grep gitup

6. Uninstall

launchctl unload ~/Library/LaunchAgents/com.user.gitup.plist
rm ~/Library/LaunchAgents/com.user.gitup.plist
uv tool uninstall gitup
rm -rf ~/.gitup        # removes bookmark list

Why this needs the worktree discipline

gitup won't clobber a dirty tree — it skips and warns. But that means if you're sitting on uncommitted edits in ~/Developer/foo, that repo silently stops auto-updating until you clean up. Drift sneaks in.

The fix: never edit the main checkout directly. Use git worktree add ../foo-feature -b my-feature (or your editor's worktree integration) so the primary checkout always tracks origin and your edits live in a sibling directory. Agents like Claude Code can be configured to enforce this — but the rule applies whether or not you use one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment