94 lines
3.5 KiB
Plaintext
94 lines
3.5 KiB
Plaintext
|
|
#!/usr/bin/env bash
|
||
|
|
# gstack-repo-mode — detect solo vs collaborative repo mode
|
||
|
|
# Usage: source <(gstack-repo-mode) → sets REPO_MODE variable
|
||
|
|
# Or: gstack-repo-mode → prints REPO_MODE=... line
|
||
|
|
#
|
||
|
|
# Detection heuristic (90-day window):
|
||
|
|
# Solo: top author >= 80% of commits
|
||
|
|
# Collaborative: top author < 80%
|
||
|
|
#
|
||
|
|
# Override: gstack-config set repo_mode solo|collaborative
|
||
|
|
# Cache: ~/.gstack/projects/$SLUG/repo-mode.json (7-day TTL)
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||
|
|
# Compute SLUG directly (avoid eval of gstack-slug — branch names can contain shell metacharacters)
|
||
|
|
REMOTE_URL=$(git remote get-url origin 2>/dev/null || true)
|
||
|
|
if [ -z "$REMOTE_URL" ]; then
|
||
|
|
echo "REPO_MODE=unknown"
|
||
|
|
exit 0
|
||
|
|
fi
|
||
|
|
SLUG=$(echo "$REMOTE_URL" | sed 's|.*[:/]\([^/]*/[^/]*\)\.git$|\1|;s|.*[:/]\([^/]*/[^/]*\)$|\1|' | tr '/' '-')
|
||
|
|
[ -z "${SLUG:-}" ] && { echo "REPO_MODE=unknown"; exit 0; }
|
||
|
|
|
||
|
|
# Validate: only allow known values (prevent shell injection via source <(...))
|
||
|
|
validate_mode() {
|
||
|
|
case "$1" in solo|collaborative|unknown) echo "$1" ;; *) echo "unknown" ;; esac
|
||
|
|
}
|
||
|
|
|
||
|
|
# Config override takes precedence
|
||
|
|
OVERRIDE=$("$SCRIPT_DIR/gstack-config" get repo_mode 2>/dev/null || true)
|
||
|
|
if [ -n "$OVERRIDE" ] && [ "$OVERRIDE" != "null" ]; then
|
||
|
|
echo "REPO_MODE=$(validate_mode "$OVERRIDE")"
|
||
|
|
exit 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Check cache (7-day TTL)
|
||
|
|
CACHE_DIR="$HOME/.gstack/projects/$SLUG"
|
||
|
|
CACHE_FILE="$CACHE_DIR/repo-mode.json"
|
||
|
|
if [ -f "$CACHE_FILE" ]; then
|
||
|
|
CACHE_AGE=$(( $(date +%s) - $(stat -f %m "$CACHE_FILE" 2>/dev/null || stat -c %Y "$CACHE_FILE" 2>/dev/null || echo 0) ))
|
||
|
|
if [ "$CACHE_AGE" -lt 604800 ]; then # 7 days in seconds
|
||
|
|
MODE=$(grep -o '"mode":"[^"]*"' "$CACHE_FILE" | head -1 | cut -d'"' -f4)
|
||
|
|
[ -n "$MODE" ] && echo "REPO_MODE=$(validate_mode "$MODE")" && exit 0
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Compute from git history (90-day window)
|
||
|
|
# Use default branch (not HEAD) to avoid feature-branch sampling bias
|
||
|
|
DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/||' || true)
|
||
|
|
# Fallback: try origin/main, then origin/master, then HEAD
|
||
|
|
if [ -z "$DEFAULT_BRANCH" ]; then
|
||
|
|
if git rev-parse --verify origin/main &>/dev/null; then
|
||
|
|
DEFAULT_BRANCH="origin/main"
|
||
|
|
elif git rev-parse --verify origin/master &>/dev/null; then
|
||
|
|
DEFAULT_BRANCH="origin/master"
|
||
|
|
else
|
||
|
|
DEFAULT_BRANCH="HEAD"
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
SHORTLOG=$(git shortlog -sn --since="90 days ago" --no-merges "$DEFAULT_BRANCH" 2>/dev/null)
|
||
|
|
if [ -z "$SHORTLOG" ]; then
|
||
|
|
echo "REPO_MODE=unknown"
|
||
|
|
exit 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Compute TOTAL from ALL authors (not truncated) to avoid solo bias
|
||
|
|
TOTAL=$(echo "$SHORTLOG" | awk '{s+=$1} END {print s}')
|
||
|
|
TOP=$(echo "$SHORTLOG" | head -1 | awk '{print $1}')
|
||
|
|
AUTHORS=$(echo "$SHORTLOG" | wc -l | tr -d ' ')
|
||
|
|
|
||
|
|
# Minimum sample: need at least 5 commits to classify
|
||
|
|
if [ "$TOTAL" -lt 5 ]; then
|
||
|
|
echo "REPO_MODE=unknown"
|
||
|
|
exit 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
TOP_PCT=$(( TOP * 100 / TOTAL ))
|
||
|
|
|
||
|
|
# Solo: top author >= 80% of commits (occasional outside PRs don't change mode)
|
||
|
|
if [ "$TOP_PCT" -ge 80 ]; then
|
||
|
|
MODE=solo
|
||
|
|
else
|
||
|
|
MODE=collaborative
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Cache result atomically (fail silently if ~/.gstack is unwritable)
|
||
|
|
mkdir -p "$CACHE_DIR" 2>/dev/null || true
|
||
|
|
CACHE_TMP=$(mktemp "$CACHE_DIR/.repo-mode-XXXXXX" 2>/dev/null || true)
|
||
|
|
if [ -n "$CACHE_TMP" ]; then
|
||
|
|
echo "{\"mode\":\"$MODE\",\"top_pct\":$TOP_PCT,\"authors\":$AUTHORS,\"total\":$TOTAL,\"computed\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" > "$CACHE_TMP" 2>/dev/null && mv "$CACHE_TMP" "$CACHE_FILE" 2>/dev/null || rm -f "$CACHE_TMP" 2>/dev/null
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo "REPO_MODE=$MODE"
|