|
| 1 | +#!/bin/sh |
| 2 | +# Rebuild the graphify code graph (graphify-out/) for this worktree. |
| 3 | +# |
| 4 | +# Called by the pre-commit "graphify" hook after commit, checkout, merge and |
| 5 | +# rewrite. Needs a POSIX sh. Always exits 0 so it never breaks a git operation. |
| 6 | +# |
| 7 | +# Every call marks the worktree "dirty" and starts a detached worker. Only one |
| 8 | +# worker per worktree holds the lock (a mkdir lock, portable to macOS); it |
| 9 | +# rebuilds until nothing marked the tree dirty meanwhile, so the final graph |
| 10 | +# always reflects the latest trigger. |
| 11 | +# |
| 12 | +# GRAPHIFY_UPDATE_FOREGROUND=1 runs the worker inline (for tests). |
| 13 | + |
| 14 | +# Stops graphify from rewriting the global agent skill files with this |
| 15 | +# project's pinned version. |
| 16 | +export GRAPHIFY_NO_AUTO_REFRESH=1 |
| 17 | + |
| 18 | +# Work from the worktree root, whatever the caller's directory (kept as is |
| 19 | +# outside a git repository). |
| 20 | +self=$(cd "$(dirname "$0")" && pwd)/$(basename "$0") |
| 21 | +root=$(git rev-parse --show-toplevel 2>/dev/null) && cd "$root" |
| 22 | + |
| 23 | +out=graphify-out |
| 24 | +lock=$out/.lock |
| 25 | +dirty=$out/.dirty |
| 26 | +log=$out/update.log |
| 27 | +stale_minutes=10 |
| 28 | +max_log_bytes=100000 |
| 29 | + |
| 30 | +say() { printf '%s %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >>"$log"; } |
| 31 | + |
| 32 | +trim_log() { |
| 33 | + size=$(wc -c <"$log" 2>/dev/null || echo 0) |
| 34 | + if [ "${size:-0}" -gt "$max_log_bytes" ]; then |
| 35 | + tail -n 200 "$log" >"$log.tmp" 2>/dev/null && mv "$log.tmp" "$log" |
| 36 | + fi |
| 37 | +} |
| 38 | + |
| 39 | +# Remove the lock only if this process still owns it. |
| 40 | +release() { |
| 41 | + [ "$(cat "$lock/pid" 2>/dev/null)" = "$$" ] && rm -rf "$lock" |
| 42 | + return 0 |
| 43 | +} |
| 44 | + |
| 45 | +# Take the lock. It is stale if its holder is dead, or if it has no pid file |
| 46 | +# and has not been touched for a while. A live holder is never robbed. |
| 47 | +acquire() { |
| 48 | + mkdir "$lock" 2>/dev/null && { echo $$ >"$lock/pid"; return 0; } |
| 49 | + pid=$(cat "$lock/pid" 2>/dev/null) |
| 50 | + if { [ -n "$pid" ] && ! kill -0 "$pid" 2>/dev/null; } || |
| 51 | + { [ -z "$pid" ] && |
| 52 | + [ -n "$(find "$lock" -maxdepth 0 -mmin +"$stale_minutes" 2>/dev/null)" ]; }; then |
| 53 | + say "removing stale lock (holder ${pid:-unknown})" |
| 54 | + rm -rf "$lock" |
| 55 | + mkdir "$lock" 2>/dev/null && { echo $$ >"$lock/pid"; return 0; } |
| 56 | + fi |
| 57 | + return 1 |
| 58 | +} |
| 59 | + |
| 60 | +worker() { |
| 61 | + while acquire; do |
| 62 | + trap release EXIT |
| 63 | + while [ -f "$dirty" ]; do |
| 64 | + touch "$lock" 2>/dev/null |
| 65 | + rm -f "$dirty" |
| 66 | + say "update start" |
| 67 | + if uv run --frozen graphify update . >>"$log" 2>&1; then |
| 68 | + say "update done" |
| 69 | + else |
| 70 | + say "update failed (exit $?)" |
| 71 | + fi |
| 72 | + trim_log |
| 73 | + done |
| 74 | + release |
| 75 | + trap - EXIT |
| 76 | + # A trigger may have arrived between the last check and the release. |
| 77 | + [ -f "$dirty" ] || return 0 |
| 78 | + done |
| 79 | +} |
| 80 | + |
| 81 | +if [ "$1" = "--worker" ]; then |
| 82 | + worker |
| 83 | + exit 0 |
| 84 | +fi |
| 85 | + |
| 86 | +command -v uv >/dev/null 2>&1 || exit 0 |
| 87 | +mkdir -p "$out" 2>/dev/null || exit 0 |
| 88 | + |
| 89 | +if [ ! -f uv.lock ]; then |
| 90 | + say "no uv.lock in this checkout; skipping (run 'uv lock' to enable the graph)" |
| 91 | + exit 0 |
| 92 | +fi |
| 93 | + |
| 94 | +touch "$dirty" |
| 95 | +if [ -n "$GRAPHIFY_UPDATE_FOREGROUND" ]; then |
| 96 | + worker |
| 97 | +elif command -v setsid >/dev/null 2>&1; then |
| 98 | + setsid sh "$self" --worker >/dev/null 2>&1 </dev/null & |
| 99 | +else |
| 100 | + nohup sh "$self" --worker >/dev/null 2>&1 </dev/null & |
| 101 | +fi |
| 102 | +exit 0 |
0 commit comments