|
| 1 | +#!/usr/bin/env bash |
| 2 | +# SessionStart-Hook: meldet, wie viele Commits der ausgecheckte Stand hinter |
| 3 | +# origin/<Default-Branch> liegt. Begruendung: .claude/hooks/README.md |
| 4 | +# |
| 5 | +# Oberste Regel, wichtiger als jede Meldung: Dieser Hook blockiert die Session |
| 6 | +# nie. Kein Netz, kein Remote, detached HEAD, flatterndes DNS, fehlende |
| 7 | +# Anmeldedaten — jeder dieser Faelle endet still mit Exit 0 und ohne Ausgabe. |
| 8 | +# Ein Hook, der bei Netzproblemen die Arbeit anhaelt, wird nach dem zweiten Mal |
| 9 | +# abgeschaltet und schuetzt danach gar nichts mehr. |
| 10 | + |
| 11 | +# Kein `set -e`: Ein fehlschlagender Befehl ist hier der Normalfall (offline), |
| 12 | +# kein Abbruchgrund. Das `trap` faengt ab, was trotzdem durchrutscht — etwa |
| 13 | +# eine ungesetzte Variable unter `set -u`. |
| 14 | +set -uo pipefail |
| 15 | +trap 'exit 0' EXIT |
| 16 | + |
| 17 | +# Sekunden, die das Netz insgesamt kosten darf. |
| 18 | +FETCH_TIMEOUT=${CLAUDE_STALE_CHECK_TIMEOUT:-8} |
| 19 | + |
| 20 | +# Ein wartender Anmeldedialog haengt den Sessionstart, ohne dass ein Timeout |
| 21 | +# auf `git` ueberhaupt greift: Der Prozess laeuft ja, er wartet nur auf eine |
| 22 | +# Eingabe, die niemand gibt. Deshalb alle Dialoge vorab abschalten. |
| 23 | +export GIT_TERMINAL_PROMPT=0 |
| 24 | +export GIT_ASKPASS=/bin/true |
| 25 | +export SSH_ASKPASS=/bin/true |
| 26 | +export GIT_SSH_COMMAND="${GIT_SSH_COMMAND:-ssh -o BatchMode=yes -o ConnectTimeout=5}" |
| 27 | +# Zweite Reissleine fuer den Fall, dass `timeout(1)` fehlt: git bricht eine |
| 28 | +# HTTP-Uebertragung ab, die so lange fast nichts mehr liefert. |
| 29 | +export GIT_HTTP_LOW_SPEED_LIMIT=1000 |
| 30 | +export GIT_HTTP_LOW_SPEED_TIME="$FETCH_TIMEOUT" |
| 31 | + |
| 32 | +mit_timeout() { |
| 33 | + if command -v timeout >/dev/null 2>&1; then |
| 34 | + timeout "$FETCH_TIMEOUT" "$@" |
| 35 | + else |
| 36 | + "$@" |
| 37 | + fi |
| 38 | +} |
| 39 | + |
| 40 | +# Den Default-Branch ermitteln, nicht `main` annehmen: Drei Server im |
| 41 | +# Portfolio (openlex-mcp, swiss-courts-mcp, swisstopo-mcp) heissen ihren |
| 42 | +# `master`. Genau diese Annahme hat schon einmal einen Branch 15 Commits alt |
| 43 | +# werden lassen, weil `origin/main` mit «couldn't find remote ref main» |
| 44 | +# scheiterte und das wie ein Netzproblem aussah. |
| 45 | +default_branch() { |
| 46 | + local zweig |
| 47 | + # Zuerst lokal, ohne Netz: `git clone` setzt diese Referenz. |
| 48 | + zweig=$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null) |
| 49 | + zweig=${zweig#origin/} |
| 50 | + if [ -n "$zweig" ]; then |
| 51 | + printf '%s\n' "$zweig" |
| 52 | + return 0 |
| 53 | + fi |
| 54 | + # Sonst den Remote fragen. Schlaegt das fehl, wird nichts geraten. |
| 55 | + mit_timeout git ls-remote --symref origin HEAD 2>/dev/null | |
| 56 | + sed -n 's|^ref: refs/heads/\([^[:space:]]*\).*|\1|p' | |
| 57 | + head -n 1 |
| 58 | +} |
| 59 | + |
| 60 | +cd "${CLAUDE_PROJECT_DIR:-.}" 2>/dev/null || exit 0 |
| 61 | + |
| 62 | +git rev-parse --git-dir >/dev/null 2>&1 || exit 0 # kein Git-Repo |
| 63 | +git rev-parse --verify --quiet HEAD >/dev/null 2>&1 || exit 0 # noch kein Commit |
| 64 | +git remote get-url origin >/dev/null 2>&1 || exit 0 # kein Remote `origin` |
| 65 | + |
| 66 | +ZWEIG=$(default_branch) |
| 67 | +[ -n "${ZWEIG:-}" ] || exit 0 |
| 68 | + |
| 69 | +# Nur dieser eine Branch, kein `--tags`, kein `--all`: Der Hook soll billig |
| 70 | +# sein. Der Aufruf aktualisiert FETCH_HEAD und die Remote-Tracking-Referenz, |
| 71 | +# er veraendert den Arbeitsbaum nicht. |
| 72 | +mit_timeout git fetch --quiet origin "$ZWEIG" >/dev/null 2>&1 || exit 0 |
| 73 | + |
| 74 | +ZIEL=$(git rev-parse --verify --quiet FETCH_HEAD 2>/dev/null) |
| 75 | +[ -n "${ZIEL:-}" ] || exit 0 |
| 76 | + |
| 77 | +# Funktioniert auch bei detached HEAD — dort ist HEAD eine gueltige Revision |
| 78 | +# wie jede andere. |
| 79 | +HINTER=$(git rev-list --count "HEAD..$ZIEL" 2>/dev/null) |
| 80 | +case "${HINTER:-}" in |
| 81 | +'' | *[!0-9]*) exit 0 ;; |
| 82 | +esac |
| 83 | + |
| 84 | +# Bei 0 schweigt der Hook. Eine Meldung «alles aktuell» bei jedem Sessionstart |
| 85 | +# waere genau das Rauschen, das man nach der dritten Woche nicht mehr liest. |
| 86 | +[ "$HINTER" -gt 0 ] || exit 0 |
| 87 | + |
| 88 | +if [ "$HINTER" -eq 1 ]; then |
| 89 | + WORT="Commit" |
| 90 | +else |
| 91 | + WORT="Commits" |
| 92 | +fi |
| 93 | + |
| 94 | +cat <<MELDUNG |
| 95 | +[Klon-Aktualitaet] Der ausgecheckte Stand liegt $HINTER $WORT hinter origin/$ZWEIG. |
| 96 | +
|
| 97 | +Ein veralteter Klon erzeugt eine rote CI, deren Ursache nicht im Diff steht: |
| 98 | +Die fehlenden Commits sind erfahrungsgemaess genau die, die das Gate |
| 99 | +einfuehren, an dem der Branch dann scheitert. Vor dem Arbeiten aktualisieren: |
| 100 | +
|
| 101 | + git fetch origin $ZWEIG && git merge FETCH_HEAD |
| 102 | +MELDUNG |
| 103 | + |
| 104 | +exit 0 |
0 commit comments