#!/usr/bin/env bash # git-cadence-commit-push.sh — optional WIP commit + push on the historical cadence # # Model (from koopa-admin-log / francpaysan-admin-log histories, 2026-07): # - message: area: short description (monitoring: … / landings: … / docs: …) # - size: small logical steps, not giant dumps # - interval: about **7–11 minutes** while work is in progress (sometimes denser) # - push: after each commit when the remote accepts credentials # # Usage: # ./scripts/git-cadence-commit-push.sh # this repo # ./scripts/git-cadence-commit-push.sh /path/to/repo # CADENCE_MSG='monitoring: wip ladder' ./scripts/git-cadence-commit-push.sh # CADENCE_DRY=1 ./scripts/git-cadence-commit-push.sh # show only # # Cron / agent every ~9 min: # */9 * * * * $HOME/src/koopa/koopa-admin-log/scripts/git-cadence-commit-push.sh # */9 * * * * $HOME/src/koopa/koopa-admin-log/scripts/git-cadence-commit-push.sh \ # $HOME/taler/src/francpaysan-admin-log # set -euo pipefail REPO="${1:-}" if [ -z "$REPO" ]; then REPO="$(cd "$(dirname "$0")/.." && pwd)" fi REPO="$(cd "$REPO" && pwd)" cd "$REPO" CADENCE_DRY="${CADENCE_DRY:-0}" # Never auto-add secrets SKIP_GLOBS=( 'secrets.env' '**/secrets.env' '**/*password*' '**/*secret*' '**/*.pem' '**/*token*' ) if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then echo "not a git repo: $REPO" >&2 exit 1 fi branch=$(git rev-parse --abbrev-ref HEAD) remote=$(git remote 2>/dev/null | head -1 || true) : "${remote:=origin}" # Dirty? (tracked + untracked, ignoring unreadable) if git diff --quiet && git diff --cached --quiet \ && [ -z "$(git ls-files --others --exclude-standard)" ]; then echo "clean · $REPO · $branch (nothing to commit)" # still try push if ahead ahead=$(git rev-list --count "${remote}/${branch}..HEAD" 2>/dev/null || echo 0) if [ "${ahead:-0}" -gt 0 ]; then echo "ahead of ${remote}/${branch} by $ahead — push" if [ "$CADENCE_DRY" = "1" ]; then echo "DRY: git push $remote $branch" else git push "$remote" "$branch" || echo "WARN: push failed (auth?)" >&2 fi fi exit 0 fi # Stage safe paths only git add -A for g in "${SKIP_GLOBS[@]}"; do git reset -q -- "$g" 2>/dev/null || true done # unstage anything still matching secret names while IFS= read -r f; do case "$f" in *secret*|*password*|*token*|secrets.env) git reset -q -- "$f" 2>/dev/null || true ;; esac done < <(git diff --cached --name-only 2>/dev/null || true) if git diff --cached --quiet; then echo "only secret/skip paths dirty — not committing · $REPO" exit 0 fi # Message from paths (historical area: style) msg="${CADENCE_MSG:-}" if [ -z "$msg" ]; then files=$(git diff --cached --name-only | head -20) area="wip" case "$files" in *taler-monitoring*|*scripts/taler-monitoring*) area="monitoring" ;; *landing*|*landings*|*configs/*landing*) area="landing" ;; *docs*|*README*|*TODO*|*NOTES*) area="docs" ;; *caddy*) area="caddy" ;; *scripts*) area="scripts" ;; esac n=$(git diff --cached --name-only | wc -l | tr -d ' ') sample=$(git diff --cached --name-only | head -1 | xargs -r basename) msg="${area}: cadence ${n} file(s) · ${sample}" fi if [ "$CADENCE_DRY" = "1" ]; then echo "DRY commit: $msg" git diff --cached --stat echo "DRY push: $remote $branch" exit 0 fi git commit -m "$msg" echo "committed · $msg" if ! git push "$remote" "$branch"; then echo "WARN: push failed for $REPO ($remote $branch) — commit is local" >&2 exit 0 fi echo "pushed · $remote $branch"