docs: cadence 7–13 min jitter; commit often, push only at end

Soft range instead of fixed 9m; helper defaults to no push.
This commit is contained in:
Hernâni Marques 2026-07-17 20:04:35 +02:00
parent 9a07eb1dfe
commit 93fe5f34f7
No known key found for this signature in database
GPG key ID: CB5738652768F7E9
2 changed files with 94 additions and 55 deletions

View file

@ -1,22 +1,24 @@
#!/usr/bin/env bash
# git-cadence-commit-push.sh — optional WIP commit + push on the historical cadence
# git-cadence-commit.sh — optional local WIP commit on a soft 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 **711 minutes** while work is in progress (sometimes denser)
# - push: after each commit when the remote accepts credentials
# Model (koopa-admin-log / francpaysan-admin-log histories + ops preference):
# - message: area: short description
# - size: small logical steps; **prefer more commits over few**
# - interval: soft range **713 minutes** while work is open (not a strict timer;
# denser bursts of 02 min are fine for tiny follow-ups)
# - push: **only at the end of a work session** (CADENCE_PUSH=1), never by default
#
# Usage:
# ./scripts/git-cadence-commit-push.sh # this repo
# ./scripts/git-cadence-commit-push.sh # commit only if dirty
# ./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
# CADENCE_DRY=1 ./scripts/git-cadence-commit-push.sh
# CADENCE_PUSH=1 ./scripts/git-cadence-commit-push.sh # explicit push (end of work)
# CADENCE_MIN_AGE_S=420 # skip if last commit younger than this (default 0)
#
# 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
# Soft timer (713 min, not fixed): fire often, skip until age ∈ [7,13] min target.
# # agent/cron every 7m — script enforces random target in range when CADENCE_JITTER=1
# CADENCE_JITTER=1 ./scripts/git-cadence-commit-push.sh
#
set -euo pipefail
@ -28,7 +30,14 @@ REPO="$(cd "$REPO" && pwd)"
cd "$REPO"
CADENCE_DRY="${CADENCE_DRY:-0}"
# Never auto-add secrets
# Push only when explicitly requested (end of session) — default OFF
CADENCE_PUSH="${CADENCE_PUSH:-0}"
# Soft range 713 minutes (seconds)
CADENCE_RANGE_MIN_S="${CADENCE_RANGE_MIN_S:-420}" # 7 min
CADENCE_RANGE_MAX_S="${CADENCE_RANGE_MAX_S:-780}" # 13 min
CADENCE_JITTER="${CADENCE_JITTER:-0}"
CADENCE_MIN_AGE_S="${CADENCE_MIN_AGE_S:-0}"
SKIP_GLOBS=(
'secrets.env'
'**/secrets.env'
@ -47,29 +56,66 @@ branch=$(git rev-parse --abbrev-ref HEAD)
remote=$(git remote 2>/dev/null | head -1 || true)
: "${remote:=origin}"
# Dirty? (tracked + untracked, ignoring unreadable)
# Jitter: only proceed if last commit is at least a random target in [7,13] min
if [ "$CADENCE_JITTER" = "1" ]; then
last=$(git log -1 --format=%ct 2>/dev/null || echo 0)
now=$(date +%s)
age=$((now - last))
# stable-ish target per hour so repeated 7m fires don't all commit
target=$(python3 -c "
import random, time
random.seed(int(time.time()) // 60) # changes every minute
print(random.randint(${CADENCE_RANGE_MIN_S}, ${CADENCE_RANGE_MAX_S}))
" 2>/dev/null || echo 600)
if [ "$age" -lt "$target" ]; then
echo "jitter wait · age=${age}s < target=${target}s (range ${CADENCE_RANGE_MIN_S}-${CADENCE_RANGE_MAX_S}s) · $REPO"
exit 0
fi
elif [ "${CADENCE_MIN_AGE_S}" -gt 0 ] 2>/dev/null; then
last=$(git log -1 --format=%ct 2>/dev/null || echo 0)
now=$(date +%s)
age=$((now - last))
if [ "$age" -lt "$CADENCE_MIN_AGE_S" ]; then
echo "min-age wait · age=${age}s < ${CADENCE_MIN_AGE_S}s · $REPO"
exit 0
fi
fi
do_push() {
local ahead
ahead=$(git rev-list --count "${remote}/${branch}..HEAD" 2>/dev/null || echo 0)
if [ "${ahead:-0}" -le 0 ]; then
echo "nothing to push · $REPO · $branch"
return 0
fi
echo "ahead of ${remote}/${branch} by $ahead"
if [ "$CADENCE_DRY" = "1" ]; then
echo "DRY: git push $remote $branch"
return 0
fi
if [ "$CADENCE_PUSH" != "1" ]; then
echo "skip push (CADENCE_PUSH!=1 — push only at end of work)"
return 0
fi
git push "$remote" "$branch" || {
echo "WARN: push failed for $REPO ($remote $branch)" >&2
return 0
}
echo "pushed · $remote $branch"
}
# Clean working tree → maybe push only
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
do_push
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)
@ -83,7 +129,6 @@ if git diff --cached --quiet; then
exit 0
fi
# Message from paths (historical area: style)
msg="${CADENCE_MSG:-}"
if [ -z "$msg" ]; then
files=$(git diff --cached --name-only | head -20)
@ -103,14 +148,10 @@ fi
if [ "$CADENCE_DRY" = "1" ]; then
echo "DRY commit: $msg"
git diff --cached --stat
echo "DRY push: $remote $branch"
[ "$CADENCE_PUSH" = "1" ] && echo "DRY push: $remote $branch" || echo "DRY: no push"
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"
do_push