Initial: gitea-api (read-only) + sporel-commit tools with Claude skills
This commit is contained in:
107
sporel-commit.sh
Executable file
107
sporel-commit.sh
Executable file
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bash
|
||||
# sporel-commit.sh — Commit + push all DIRTY local Sporel repos with one message.
|
||||
#
|
||||
# Usage: sporel-commit.sh [--dry-run] "<commit message>"
|
||||
# Example: sporel-commit.sh "lib-core: bump manifests to 0.4.0"
|
||||
# sporel-commit.sh --dry-run "wip"
|
||||
#
|
||||
# For each git repo under $SPOREL_ROOT (recursed, any depth) with uncommitted
|
||||
# changes, it:
|
||||
# - picks the author from that repo's OWN last commit (git log -1 %an),
|
||||
# because Sporel repos have MIXED authors (Calic vs Axel Meyer);
|
||||
# email is always axel.meyer@durania.net
|
||||
# - git add -A; git commit -m "<message>"
|
||||
# - then pushes IF the branch is ahead and not diverged
|
||||
#
|
||||
# Push safety: CI pipelines trigger on TAGS (v0.x.0), not on master pushes,
|
||||
# so committing/pushing WIP to master does not start pipelines.
|
||||
#
|
||||
# Diverged repos (behind AND ahead after commit) are reported and left for
|
||||
# manual rebase — never force-pushed.
|
||||
#
|
||||
# Config: $SPOREL_ROOT (default /root/projects/Sporel)
|
||||
# Exit: 0 all good · 2 attention (diverged/skipped) · 1 hard error
|
||||
set -uo pipefail
|
||||
|
||||
DRY=false
|
||||
if [ "${1:-}" = "--dry-run" ]; then DRY=true; shift; fi
|
||||
MSG="${1:-}"
|
||||
if [ -z "$MSG" ]; then
|
||||
echo "usage: sporel-commit.sh [--dry-run] \"<commit message>\"" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
ROOT="${SPOREL_ROOT:-/root/projects/Sporel}"
|
||||
EMAIL="axel.meyer@durania.net"
|
||||
[ -d "$ROOT" ] || { echo "ERROR: Sporel root not found: $ROOT" >&2; exit 1; }
|
||||
|
||||
declare -a ATTN=() ERRS=()
|
||||
committed=0; pushed=0; clean=0
|
||||
|
||||
mapfile -t REPOS < <(find "$ROOT" -maxdepth 6 -name .git -type d 2>/dev/null | sed 's,/\.git$,,' | sort)
|
||||
|
||||
printf '%-50s %s\n' "REPO" "ACTION"
|
||||
printf '%-50s %s\n' "--------------------------------------------------" "------"
|
||||
|
||||
for repo in "${REPOS[@]}"; do
|
||||
name="${repo#"$ROOT"/}"
|
||||
cd "$repo" 2>/dev/null || { printf '%-50s %s\n' "$name" "ERROR cd"; ERRS+=("$name: cd failed"); continue; }
|
||||
|
||||
if [ -z "$(git status --porcelain 2>/dev/null)" ]; then
|
||||
clean=$((clean+1)); continue # nothing to commit, stay quiet
|
||||
fi
|
||||
|
||||
branch="$(git branch --show-current 2>/dev/null)"
|
||||
if [ -z "$branch" ]; then
|
||||
printf '%-50s %s\n' "$name" "DETACHED (skip)"; ATTN+=("$name: detached HEAD, has changes"); continue
|
||||
fi
|
||||
|
||||
author="$(git log -1 --pretty=%an 2>/dev/null)"; author="${author:-Calic}"
|
||||
|
||||
if $DRY; then
|
||||
files="$(git status --porcelain | wc -l | tr -d ' ')"
|
||||
printf '%-50s %s\n' "$name" "would commit $files file(s) as '$author'"
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! cerr="$(git -c user.name="$author" -c user.email="$EMAIL" commit -aqm "$MSG" 2>&1)"; then
|
||||
printf '%-50s %s\n' "$name" "ERROR commit"; ERRS+=("$name: commit failed: ${cerr:-unknown}"); continue
|
||||
fi
|
||||
committed=$((committed+1))
|
||||
|
||||
# push only if upstream exists, ahead, not diverged
|
||||
if ! git rev-parse --abbrev-ref '@{u}' >/dev/null 2>&1; then
|
||||
printf '%-50s %s\n' "$name" "COMMITTED ($author) — no upstream, NOT pushed"
|
||||
ATTN+=("$name: committed but no upstream to push to"); continue
|
||||
fi
|
||||
git fetch --quiet origin 2>/dev/null || true
|
||||
counts="$(git rev-list --left-right --count '@{u}...HEAD' 2>/dev/null || echo '0 0')"
|
||||
behind="${counts%%[[:space:]]*}"; ahead="${counts##*[[:space:]]}"
|
||||
if [ "${behind:-0}" -gt 0 ]; then
|
||||
printf '%-50s %s\n' "$name" "COMMITTED ($author) — DIVERGED, NOT pushed"
|
||||
ATTN+=("$name: committed but diverged (behind $behind) — rebase then push manually"); continue
|
||||
fi
|
||||
if perr="$(git push origin "$branch" 2>&1)"; then
|
||||
printf '%-50s %s\n' "$name" "COMMITTED + PUSHED ($author) up${ahead:-?}"
|
||||
pushed=$((pushed+1))
|
||||
else
|
||||
printf '%-50s %s\n' "$name" "COMMITTED ($author) — ERROR push"
|
||||
ERRS+=("$name: push failed: ${perr:-unknown}")
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
if $DRY; then
|
||||
echo "Dry-run: ${#REPOS[@]} repos scanned, $clean clean. No changes made."
|
||||
exit 0
|
||||
fi
|
||||
echo "Summary: ${#REPOS[@]} repos | committed=$committed pushed=$pushed clean=$clean | attention=${#ATTN[@]} errors=${#ERRS[@]}"
|
||||
[ "${#ATTN[@]}" -gt 0 ] && { echo; echo "ATTENTION:"; printf ' - %s\n' "${ATTN[@]}"; }
|
||||
[ "${#ERRS[@]}" -gt 0 ] && { echo; echo "ERRORS:"; printf ' - %s\n' "${ERRS[@]}"; }
|
||||
|
||||
if [ "${#ERRS[@]}" -gt 0 ]; then
|
||||
echo; echo "SELF-MAINTAIN: record errors in this repo's findings, diagnose, improve the script if its output was insufficient."
|
||||
exit 1
|
||||
fi
|
||||
[ "${#ATTN[@]}" -gt 0 ] && exit 2
|
||||
exit 0
|
||||
Reference in New Issue
Block a user