diff options
Diffstat (limited to 'scripts')
| -rwxr-xr-x | scripts/roam-sync.sh | 38 | ||||
| -rw-r--r-- | scripts/systemd/roam-sync.service | 9 | ||||
| -rw-r--r-- | scripts/systemd/roam-sync.timer | 10 | ||||
| -rw-r--r-- | scripts/tests/roam-sync.bats | 71 |
4 files changed, 128 insertions, 0 deletions
diff --git a/scripts/roam-sync.sh b/scripts/roam-sync.sh new file mode 100755 index 0000000..55422ec --- /dev/null +++ b/scripts/roam-sync.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# roam-sync.sh — git auto-sync for the org-roam knowledge base (spec D8). +# +# Commit any local changes, rebase onto the remote, push. Run by the +# roam-sync systemd user timer (scripts/systemd/) every 15 minutes so +# Craig's hand edits travel without a manual git step. Agents don't need +# this — they pull/commit/push inline per claude-rules/knowledge-base.md. +# +# On a rebase conflict: abort the rebase (never leave the repo mid-rebase +# for a timer to mangle), keep the local commit, exit 1 so the failure is +# visible in `systemctl --user status roam-sync`. +# +# Usage: roam-sync.sh [repo-path] (default ~/org/roam) + +set -euo pipefail + +repo="${1:-$HOME/org/roam}" + +if ! git -C "$repo" rev-parse --git-dir >/dev/null 2>&1; then + echo "roam-sync: $repo is not a git repo" >&2 + exit 1 +fi + +cd "$repo" + +# Commit local changes first so the rebase replays them. +if [ -n "$(git status --porcelain)" ]; then + git add -A + git commit -qm "chore: auto-sync $(date '+%Y-%m-%d %H:%M %z')" +fi + +if ! git pull --rebase -q 2>&1; then + git rebase --abort 2>/dev/null || true + echo "roam-sync: rebase conflict in $repo — local commit kept, resolve by hand" >&2 + exit 1 +fi + +git push -q diff --git a/scripts/systemd/roam-sync.service b/scripts/systemd/roam-sync.service new file mode 100644 index 0000000..e4ad502 --- /dev/null +++ b/scripts/systemd/roam-sync.service @@ -0,0 +1,9 @@ +# org-roam KB auto-sync (spec D8). Install: +# cp scripts/systemd/roam-sync.* ~/.config/systemd/user/ +# systemctl --user daemon-reload && systemctl --user enable --now roam-sync.timer +[Unit] +Description=Auto-sync the org-roam knowledge base repo + +[Service] +Type=oneshot +ExecStart=%h/code/rulesets/scripts/roam-sync.sh %h/org/roam diff --git a/scripts/systemd/roam-sync.timer b/scripts/systemd/roam-sync.timer new file mode 100644 index 0000000..3d65be0 --- /dev/null +++ b/scripts/systemd/roam-sync.timer @@ -0,0 +1,10 @@ +[Unit] +Description=Auto-sync the org-roam knowledge base every 15 minutes + +[Timer] +OnBootSec=2min +OnUnitActiveSec=15min +RandomizedDelaySec=60 + +[Install] +WantedBy=timers.target diff --git a/scripts/tests/roam-sync.bats b/scripts/tests/roam-sync.bats new file mode 100644 index 0000000..017a92c --- /dev/null +++ b/scripts/tests/roam-sync.bats @@ -0,0 +1,71 @@ +#!/usr/bin/env bats +# Tests for scripts/roam-sync.sh — the git auto-sync loop the roam KB timer runs. +# Each test builds a throwaway clone + bare remote pair under $BATS_TEST_TMPDIR. + +setup() { + SCRIPT="$BATS_TEST_DIRNAME/../roam-sync.sh" + export GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null + export GIT_AUTHOR_NAME=test GIT_AUTHOR_EMAIL=t@t GIT_COMMITTER_NAME=test GIT_COMMITTER_EMAIL=t@t + REMOTE="$BATS_TEST_TMPDIR/remote.git" + REPO="$BATS_TEST_TMPDIR/repo" + OTHER="$BATS_TEST_TMPDIR/other" + git init -q --bare -b main "$REMOTE" + git init -q -b main "$REPO" + (cd "$REPO" && echo seed > seed.org && git add -A && git commit -qm seed \ + && git remote add origin "$REMOTE" && git push -qu origin main) +} + +clone_other() { + git clone -q "$REMOTE" "$OTHER" 2>/dev/null +} + +@test "clean tree, current remote: exits 0 with no new commit" { + before=$(git -C "$REPO" rev-parse HEAD) + run "$SCRIPT" "$REPO" + [ "$status" -eq 0 ] + [ "$(git -C "$REPO" rev-parse HEAD)" = "$before" ] +} + +@test "dirty tree: commits and pushes to the remote" { + echo "new fact" > "$REPO/fact.org" + run "$SCRIPT" "$REPO" + [ "$status" -eq 0 ] + [ -z "$(git -C "$REPO" status --porcelain)" ] + git -C "$REMOTE" log --format=%s main | grep -q "auto-sync" +} + +@test "remote ahead: pulls the remote commit" { + clone_other + (cd "$OTHER" && echo upstream > up.org && git add -A && git commit -qm upstream && git push -q) + run "$SCRIPT" "$REPO" + [ "$status" -eq 0 ] + [ -f "$REPO/up.org" ] +} + +@test "local dirty and remote ahead, disjoint files: both land" { + clone_other + (cd "$OTHER" && echo upstream > up.org && git add -A && git commit -qm upstream && git push -q) + echo "local fact" > "$REPO/local.org" + run "$SCRIPT" "$REPO" + [ "$status" -eq 0 ] + [ -f "$REPO/up.org" ] + git -C "$REMOTE" log --format=%s main | grep -q "auto-sync" +} + +@test "conflicting rebase: exits nonzero and leaves no rebase in progress" { + clone_other + (cd "$OTHER" && echo theirs > seed.org && git add -A && git commit -qm theirs && git push -q) + echo mine > "$REPO/seed.org" + run "$SCRIPT" "$REPO" + [ "$status" -ne 0 ] + [ ! -d "$REPO/.git/rebase-merge" ] + [ ! -d "$REPO/.git/rebase-apply" ] + # the local change survives as a commit for the human to resolve later + git -C "$REPO" log --format=%s | grep -q "auto-sync" +} + +@test "missing repo path: exits nonzero with a message" { + run "$SCRIPT" "$BATS_TEST_TMPDIR/nonexistent" + [ "$status" -ne 0 ] + [[ "$output" == *"not a git repo"* ]] +} |
