99e5641127
Pipeline (3 operator asks): - bump-release.sh: canonical orchestrator (forks + sources + external) - upgrade-forks.sh --to=<tag>: rebase forks with diverged-mode guard - bump-graphics-recipes.sh: map-driven group-aware graphics bumps - check-external-versions.sh: drift checker for Qt6/KF6/Plasma/Mesa/Wayland - refresh-fork-upstream-map.sh: append-only map updater with --check - post-checkout-version-sync.sh + install-git-hooks.sh: opt-in branch hook - external_version_lib.py: shared version-parsing/bumping library - external-upstream-map.toml: ~80 external package entries - bump-fork.sh: deprecated (REDBEAR_I_KNOW_BUMP_FORK_IS_DEPRECATED=1) - RELEASE-BUMP-WORKFLOW.md: operator runbook Quality fixes (8 defects from two independent audits): - blake2b stable cache keys (was hash(), non-portable) - atomic cache writes via os.replace - version_sort_key pre-release demotion (was sorting after finals) - apply_ver_transform re.error tolerance - grep || true (pipefail abort) - cd failure detection in upgrade-forks - sed URL escape (injection hardening) - refresh-fork-upstream-map last-row drop fix Doc cleanup: - Archive 5 obsolete plans to local/docs/archived/ - Remove 14 stale/superseded docs - Update 18 docs to reference bump-release.sh and fix inbound links - TOOLS.md drift fixes
201 lines
7.2 KiB
Bash
Executable File
201 lines
7.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# install-git-hooks.sh — Idempotent installer for Red Bear OS optional git hooks.
|
|
#
|
|
# Hooks in Red Bear OS are ALWAYS opt-in (per AGENTS.md "absolutely NEVER
|
|
# DELETE, NEVER IGNORE" rule — operators explicitly choose to install them).
|
|
# This script is a convenience wrapper around the manual `cp` instructions
|
|
# documented in local/docs/HOOKS.md. It never silently clobbers an existing
|
|
# hook: if a hook file already exists and differs from the canonical copy,
|
|
# it is backed up to `<name>.bak` before the new one is written, and a
|
|
# warning is printed.
|
|
#
|
|
# Usage:
|
|
# ./local/scripts/install-git-hooks.sh # install post-checkout only (default)
|
|
# ./local/scripts/install-git-hooks.sh --all # post-checkout + pre-push + commit-msg
|
|
# ./local/scripts/install-git-hooks.sh --uninstall # remove hooks installed by this script
|
|
# ./local/scripts/install-git-hooks.sh --uninstall --all
|
|
#
|
|
# Exit codes:
|
|
# 0 success (or already-installed, or nothing to uninstall)
|
|
# 1 fatal misconfiguration (not a git repo, scripts missing, etc.)
|
|
#
|
|
# See: local/docs/HOOKS.md (hook inventory + bypass instructions),
|
|
# local/docs/RELEASE-BUMP-WORKFLOW.md (release-bump pipeline).
|
|
|
|
set -euo pipefail
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Resolve repo root. Must be a Red Bear OS checkout (has local/scripts/).
|
|
# ---------------------------------------------------------------------------
|
|
|
|
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
|
if [[ -z "$ROOT" ]]; then
|
|
echo "install-git-hooks: not inside a git work tree" >&2
|
|
exit 1
|
|
fi
|
|
|
|
SCRIPTS_DIR="$ROOT/local/scripts"
|
|
HOOKS_DIR="$(git rev-parse --git-path hooks 2>/dev/null)"
|
|
# `git rev-parse --git-path hooks` resolves correctly for linked worktrees
|
|
# and custom $GIT_DIR. Fall back to the conventional path if it fails.
|
|
if [[ -z "$HOOKS_DIR" ]]; then
|
|
GIT_DIR="$(git rev-parse --git-dir 2>/dev/null || echo .git)"
|
|
[[ "$GIT_DIR" = /* ]] || GIT_DIR="$ROOT/$GIT_DIR"
|
|
HOOKS_DIR="$GIT_DIR/hooks"
|
|
fi
|
|
|
|
mkdir -p "$HOOKS_DIR"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Parse flags.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
INSTALL_ALL=0
|
|
UNINSTALL=0
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--all) INSTALL_ALL=1 ;;
|
|
--uninstall) UNINSTALL=1 ;;
|
|
-h|--help)
|
|
sed -n '1,30p' "$0"
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "install-git-hooks: unknown option: $arg" >&2
|
|
echo " usage: $0 [--all] [--uninstall]" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Hook manifest. The default install set is intentionally minimal (only
|
|
# post-checkout) so that a bare `install-git-hooks.sh` does the least
|
|
# surprising thing. `--all` extends to the pre-push and commit-msg hooks.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Each entry: <hook-name>;<source-script-relative-to-local/scripts>
|
|
HOOKS_DEFAULT=(
|
|
"post-checkout;post-checkout-version-sync.sh"
|
|
)
|
|
|
|
HOOKS_EXTRA=(
|
|
"pre-push;pre-push-checks.sh"
|
|
"commit-msg;commit-msg"
|
|
)
|
|
|
|
# Build the active set from the flags.
|
|
ACTIVE_HOOKS=()
|
|
if [[ $UNINSTALL -eq 1 ]]; then
|
|
# Uninstall needs to know the full set that --all would have installed,
|
|
# so a default-install-then---all-uninstall cleans everything up.
|
|
for entry in "${HOOKS_DEFAULT[@]}" "${HOOKS_EXTRA[@]}"; do
|
|
ACTIVE_HOOKS+=("$entry")
|
|
done
|
|
if [[ $INSTALL_ALL -eq 0 ]]; then
|
|
# Without --all, only uninstall the default set (the ones a default
|
|
# install would have placed). Operators who used --all should pass
|
|
# --uninstall --all.
|
|
ACTIVE_HOOKS=("${HOOKS_DEFAULT[@]}")
|
|
fi
|
|
else
|
|
ACTIVE_HOOKS=("${HOOKS_DEFAULT[@]}")
|
|
[[ $INSTALL_ALL -eq 1 ]] && ACTIVE_HOOKS+=("${HOOKS_EXTRA[@]}")
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Install / uninstall loop.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
install_one() {
|
|
local hook_name="$1"
|
|
local src_rel="$2"
|
|
local src="$SCRIPTS_DIR/$src_rel"
|
|
local dst="$HOOKS_DIR/$hook_name"
|
|
|
|
if [[ ! -f "$src" ]]; then
|
|
echo "install-git-hooks: WARN source script not found: $src_rel — skipping $hook_name" >&2
|
|
return 0
|
|
fi
|
|
|
|
if [[ -e "$dst" || -L "$dst" ]]; then
|
|
# Content-based, not path-based: a copied hook has a different path
|
|
# than the source, so only byte comparison detects "already current".
|
|
if cmp -s "$dst" "$src" 2>/dev/null; then
|
|
echo "install-git-hooks: $hook_name already up to date ($dst)"
|
|
chmod +x "$dst" 2>/dev/null || true
|
|
return 0
|
|
fi
|
|
# Existing file differs — back it up before overwriting.
|
|
local bak="${dst}.bak"
|
|
# Rotate any pre-existing .bak so we don't destroy the first backup.
|
|
if [[ -e "$bak" ]]; then
|
|
local i=1
|
|
while [[ -e "${dst}.bak.${i}" && $i -le 100 ]]; do i=$((i+1)); done
|
|
if [[ $i -gt 100 ]]; then
|
|
bak="${dst}.bak.$(date +%Y%m%d%H%M%S)-$$"
|
|
else
|
|
bak="${dst}.bak.${i}"
|
|
fi
|
|
fi
|
|
echo "install-git-hooks: WARN existing $hook_name differs — backing up to $bak" >&2
|
|
mv "$dst" "$bak"
|
|
fi
|
|
|
|
cp "$src" "$dst"
|
|
chmod +x "$dst"
|
|
echo "install-git-hooks: installed $hook_name -> $dst"
|
|
}
|
|
|
|
uninstall_one() {
|
|
local hook_name="$1"
|
|
local dst="$HOOKS_DIR/$hook_name"
|
|
|
|
if [[ ! -e "$dst" && ! -L "$dst" ]]; then
|
|
echo "install-git-hooks: $hook_name not present ($dst) — nothing to remove"
|
|
return 0
|
|
fi
|
|
rm -f "$dst"
|
|
echo "install-git-hooks: removed $hook_name ($dst)"
|
|
# Restore the most recent backup if one exists, so uninstall is reversible.
|
|
if [[ -e "${dst}.bak" ]]; then
|
|
mv "${dst}.bak" "$dst"
|
|
chmod +x "$dst" 2>/dev/null || true
|
|
echo "install-git-hooks: restored previous $hook_name from ${dst}.bak"
|
|
fi
|
|
}
|
|
|
|
echo "=== Red Bear OS git hook installer ==="
|
|
echo " repo root: $ROOT"
|
|
echo " hooks dir: $HOOKS_DIR"
|
|
echo " mode: $([[ $UNINSTALL -eq 1 ]] && echo 'uninstall' || echo 'install') ($([[ $INSTALL_ALL -eq 1 ]] && echo 'all hooks' || echo 'default: post-checkout only'))"
|
|
echo ""
|
|
|
|
if [[ $UNINSTALL -eq 1 ]]; then
|
|
for entry in "${ACTIVE_HOOKS[@]}"; do
|
|
hook_name="${entry%%;*}"
|
|
uninstall_one "$hook_name"
|
|
done
|
|
echo ""
|
|
echo "Uninstall complete. Remaining hooks (if any):"
|
|
ls -1 "$HOOKS_DIR" 2>/dev/null | grep -vE '\.bak(\.[0-9]+)?$' || echo " (none)"
|
|
exit 0
|
|
fi
|
|
|
|
for entry in "${ACTIVE_HOOKS[@]}"; do
|
|
hook_name="${entry%%;*}"
|
|
src_rel="${entry##*;}"
|
|
install_one "$hook_name" "$src_rel"
|
|
done
|
|
|
|
echo ""
|
|
echo "=== Install complete ==="
|
|
echo ""
|
|
echo "Installed hooks are OPT-IN and never auto-commit, auto-push, or block"
|
|
echo "checkouts. Bypass / configuration:"
|
|
echo " post-checkout: REDBEAR_NO_AUTO_SYNC=1 git checkout <branch>"
|
|
echo " pre-push: REDBEAR_SKIP_PRE_PUSH=1 git push (or git push --no-verify)"
|
|
echo ""
|
|
echo "Full documentation: local/docs/HOOKS.md"
|
|
echo "Release-bump runbook: local/docs/RELEASE-BUMP-WORKFLOW.md"
|