BMAD-METHOD/.githooks/pre-push

136 lines
5.0 KiB
Bash
Executable File

#!/bin/bash
# .githooks/pre-push
# Git hook for BMAD-METHOD contributors to enforce clean git workflow
#
# This hook ensures:
# 1. Upstream remote is configured
# 2. No direct pushes to main
# 3. Local main is synced with upstream
# 4. Branch is rebased on main
# 5. Single-commit-per-branch workflow
set -e
# Color codes for output
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
UPSTREAM_REMOTE="upstream"
UPSTREAM_URL="git@github.com:bmad-code-org/BMAD-METHOD.git"
MAIN_BRANCH="main"
echo -e "${BLUE}[pre-push] Running pre-push checks...${NC}"
# Get current branch
CURRENT_BRANCH=$(git branch --show-current)
# Read push details from stdin (remote and url)
while read local_ref local_sha remote_ref remote_sha; do
# Extract branch name from ref
if [[ "$remote_ref" =~ refs/heads/(.+) ]]; then
PUSH_BRANCH="${BASH_REMATCH[1]}"
else
continue
fi
# 1. Block direct push to main
if [ "$PUSH_BRANCH" = "$MAIN_BRANCH" ]; then
echo -e "${RED}❌ ERROR: Direct push to '$MAIN_BRANCH' is not allowed!${NC}"
echo -e "${YELLOW}The main branch should only be updated by syncing with upstream.${NC}"
echo -e "${YELLOW}To update main, run:${NC}"
echo -e " git checkout main && git pull upstream main"
exit 1
fi
done
# 2. Ensure upstream remote exists
if ! git remote | grep -q "^${UPSTREAM_REMOTE}$"; then
echo -e "${RED}❌ ERROR: Upstream remote '${UPSTREAM_REMOTE}' not configured!${NC}"
echo -e "${YELLOW}Add it with:${NC}"
echo -e " git remote add ${UPSTREAM_REMOTE} ${UPSTREAM_URL}"
exit 1
fi
# Verify upstream URL
CURRENT_UPSTREAM=$(git remote get-url "$UPSTREAM_REMOTE" 2>/dev/null || echo "")
if [[ "$CURRENT_UPSTREAM" != *"bmad-code-org/BMAD-METHOD"* ]]; then
echo -e "${YELLOW}⚠️ WARNING: Upstream remote doesn't point to bmad-code-org/BMAD-METHOD${NC}"
echo -e "${YELLOW}Current: $CURRENT_UPSTREAM${NC}"
echo -e "${YELLOW}Expected: ${UPSTREAM_URL}${NC}"
fi
# 3. Fetch upstream
echo -e "${BLUE}Fetching upstream...${NC}"
if ! git fetch "$UPSTREAM_REMOTE" --quiet 2>/dev/null; then
echo -e "${RED}❌ ERROR: Failed to fetch from upstream${NC}"
echo -e "${YELLOW}Check your network connection and SSH keys${NC}"
exit 1
fi
# 4. Check if local main is synced with upstream
LOCAL_MAIN=$(git rev-parse "$MAIN_BRANCH" 2>/dev/null || echo "")
UPSTREAM_MAIN=$(git rev-parse "${UPSTREAM_REMOTE}/${MAIN_BRANCH}" 2>/dev/null || echo "")
if [ -n "$LOCAL_MAIN" ] && [ -n "$UPSTREAM_MAIN" ] && [ "$LOCAL_MAIN" != "$UPSTREAM_MAIN" ]; then
# Check if local main is behind
if git merge-base --is-ancestor "$LOCAL_MAIN" "$UPSTREAM_MAIN"; then
echo -e "${YELLOW}⚠️ WARNING: Your local '$MAIN_BRANCH' is behind upstream${NC}"
echo -e "${YELLOW}Sync it with:${NC}"
echo -e " git checkout $MAIN_BRANCH && git pull $UPSTREAM_REMOTE $MAIN_BRANCH"
echo -e "${YELLOW}Then rebase your branch:${NC}"
echo -e " git checkout $CURRENT_BRANCH && git rebase $MAIN_BRANCH"
echo ""
# This is a warning, not blocking (allows push but warns)
elif ! git merge-base --is-ancestor "$UPSTREAM_MAIN" "$LOCAL_MAIN"; then
echo -e "${RED}❌ ERROR: Your local '$MAIN_BRANCH' has diverged from upstream${NC}"
echo -e "${YELLOW}Reset it with:${NC}"
echo -e " git checkout $MAIN_BRANCH"
echo -e " git reset --hard $UPSTREAM_REMOTE/$MAIN_BRANCH"
exit 1
fi
fi
# 5. Check branch is rebased on main
MERGE_BASE=$(git merge-base "$CURRENT_BRANCH" "$MAIN_BRANCH")
MAIN_HEAD=$(git rev-parse "$MAIN_BRANCH")
if [ "$MERGE_BASE" != "$MAIN_HEAD" ]; then
echo -e "${RED}❌ ERROR: Branch '$CURRENT_BRANCH' is not rebased on latest '$MAIN_BRANCH'${NC}"
echo -e "${YELLOW}Rebase with:${NC}"
echo -e " git rebase $MAIN_BRANCH"
exit 1
fi
# 6. Enforce single commit rule
COMMIT_COUNT=$(git rev-list --count "${MAIN_BRANCH}..${CURRENT_BRANCH}")
if [ "$COMMIT_COUNT" -eq 0 ]; then
echo -e "${RED}❌ ERROR: No commits to push (branch is at same state as $MAIN_BRANCH)${NC}"
exit 1
elif [ "$COMMIT_COUNT" -gt 1 ]; then
echo -e "${RED}❌ ERROR: Too many commits! Found $COMMIT_COUNT commits, expected exactly 1${NC}"
echo -e "${YELLOW}This repo uses a single-commit-per-branch workflow.${NC}"
echo ""
echo -e "${YELLOW}Option 1: Squash all commits into one:${NC}"
echo -e " git reset --soft $MAIN_BRANCH"
echo -e " git commit -m 'feat: your feature description'"
echo ""
echo -e "${YELLOW}Option 2: Amend existing commits:${NC}"
echo -e " git add <modified-files>"
echo -e " git commit --amend --no-edit"
echo ""
echo -e "${YELLOW}Then force push with:${NC}"
echo -e " git push --force-with-lease"
exit 1
fi
echo -e "${GREEN}✓ All pre-push checks passed!${NC}"
echo -e "${GREEN}✓ Upstream is configured and synced${NC}"
echo -e "${GREEN}✓ Branch is rebased on main${NC}"
echo -e "${GREEN}✓ Single commit workflow maintained ($COMMIT_COUNT commit)${NC}"
exit 0