# Setting Up Branch Protection

Complete workflow for configuring production-ready branch protection: required approvals, status checks, merge restrictions, push restrictions, and dismiss stale approvals.

## 1. Create Branch Protection Rule

```bash
curl -s -X POST "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/branch_protections" \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "branch_name": "{branch}",
    "enable_push": true,
    "enable_push_whitelist": true,
    "push_whitelist_usernames": ["release-bot"],
    "push_whitelist_teams": ["maintainers"],
    "push_whitelist_deploy_keys": false,
    "enable_merge_whitelist": false,
    "enable_status_check": true,
    "status_check_contexts": ["ci/build", "ci/test", "ci/lint"],
    "required_approvals": 2,
    "enable_approvals_whitelist": false,
    "block_on_rejected_reviews": true,
    "block_on_official_review_requests": true,
    "block_on_outdated_branch": true,
    "dismiss_stale_approvals": true,
    "require_signed_commits": false,
    "protected_file_patterns": "*.lock;Makefile",
    "unprotected_file_patterns": "docs/**"
  }'
```

### Key Fields Explained

| Field | Description |
|-------|-------------|
| `branch_name` | Branch name or glob pattern (e.g., `release/*`) |
| `enable_push` | Allow direct pushes (set `false` to force PR-only) |
| `enable_push_whitelist` | Restrict who can push directly |
| `push_whitelist_usernames` | Users allowed to push directly |
| `push_whitelist_teams` | Teams allowed to push directly |
| `enable_merge_whitelist` | Restrict who can merge PRs |
| `enable_status_check` | Require CI status checks to pass |
| `status_check_contexts` | Specific check names that must pass |
| `required_approvals` | Minimum number of approving reviews |
| `block_on_rejected_reviews` | Block merge if any review requests changes |
| `block_on_official_review_requests` | Block merge if requested reviewers haven't reviewed |
| `block_on_outdated_branch` | Block merge if PR branch is behind base |
| `dismiss_stale_approvals` | Dismiss approvals when new commits are pushed |
| `require_signed_commits` | Require GPG-signed commits |
| `protected_file_patterns` | Files that can only be changed by whitelisted users |
| `unprotected_file_patterns` | Files exempt from protection |

## 2. Configure Status Checks

First, identify the check context names from existing CI runs:

```bash
# Get a recent commit SHA from master
SHA=$(curl -s "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/branches/{branch}" \
  -H "Authorization: token ${FORGEJO_PAT}" | jq -r '.commit.id')

# List status checks that have been reported
curl -s "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/commits/${SHA}/statuses" \
  -H "Authorization: token ${FORGEJO_PAT}" | jq -r '.[].context' | sort -u
```

Then update the protection rule with the correct context names:

```bash
# Get existing protection rule name
RULE_NAME="{rule_name}"

curl -s -X PATCH "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/branch_protections/${RULE_NAME}" \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "enable_status_check": true,
    "status_check_contexts": ["ci/build", "ci/test", "ci/lint", "security/scan"]
  }'
```

## 3. Set Up Merge Whitelist

Restrict who can merge PRs:

```bash
curl -s -X PATCH "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/branch_protections/${RULE_NAME}" \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "enable_merge_whitelist": true,
    "merge_whitelist_usernames": ["{user1}", "{user2}", "{bot}"],
    "merge_whitelist_teams": ["maintainers"]
  }'
```

## 4. Configure Push Restrictions

For maximum security, disable direct pushes entirely:

```bash
curl -s -X PATCH "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/branch_protections/${RULE_NAME}" \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "enable_push": false
  }'
```

Or allow only specific users/bots:

```bash
curl -s -X PATCH "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/branch_protections/${RULE_NAME}" \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "enable_push": true,
    "enable_push_whitelist": true,
    "push_whitelist_usernames": ["release-bot"],
    "push_whitelist_teams": [],
    "push_whitelist_deploy_keys": false
  }'
```

## 5. Set Dismiss Stale Approvals

Ensure that new commits invalidate existing approvals:

```bash
curl -s -X PATCH "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/branch_protections/${RULE_NAME}" \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "dismiss_stale_approvals": true,
    "block_on_rejected_reviews": true,
    "block_on_official_review_requests": true
  }'
```

## 6. Test the Protection

Verify that the protection rule is in effect by checking if a PR without required approvals or failing checks cannot be merged:

```bash
# List all branch protections
curl -s "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/branch_protections" \
  -H "Authorization: token ${FORGEJO_PAT}" | jq '.[] | {
    branch_name,
    enable_push,
    enable_push_whitelist,
    push_whitelist_usernames,
    enable_merge_whitelist,
    merge_whitelist_usernames,
    enable_status_check,
    status_check_contexts,
    required_approvals,
    dismiss_stale_approvals,
    block_on_rejected_reviews,
    block_on_outdated_branch
  }'

# Try to merge a PR without approvals — should fail with 405 or 409
PR_INDEX="{index}"
MERGE_RESULT=$(curl -s -w "\n%{http_code}" -X POST \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/${PR_INDEX}/merge" \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{"Do": "merge"}')
HTTP_CODE=$(echo "$MERGE_RESULT" | tail -n 1)
BODY=$(echo "$MERGE_RESULT" | head -n -1)
echo "Merge attempt returned HTTP ${HTTP_CODE}"
echo "$BODY" | jq .
# Expected: 405 Method Not Allowed with message about unmet requirements
```

## Complete Production Setup Script

```bash
#!/bin/bash
set -euo pipefail

OWNER="{owner}"
REPO="{repo}"
BRANCH="{branch}"

echo "=== Setting up branch protection for ${OWNER}/${REPO}:${BRANCH} ==="

# Create comprehensive protection rule
curl -s -X POST "${FORGEJO_URL}/api/v1/repos/${OWNER}/${REPO}/branch_protections" \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "branch_name": "'"${BRANCH}"'",
    "enable_push": true,
    "enable_push_whitelist": true,
    "push_whitelist_usernames": ["release-bot"],
    "push_whitelist_teams": ["maintainers"],
    "push_whitelist_deploy_keys": false,
    "enable_merge_whitelist": true,
    "merge_whitelist_usernames": ["release-bot"],
    "merge_whitelist_teams": ["maintainers"],
    "enable_status_check": true,
    "status_check_contexts": ["ci/build", "ci/test", "ci/lint"],
    "required_approvals": 2,
    "enable_approvals_whitelist": false,
    "block_on_rejected_reviews": true,
    "block_on_official_review_requests": true,
    "block_on_outdated_branch": true,
    "dismiss_stale_approvals": true,
    "require_signed_commits": false,
    "protected_file_patterns": "",
    "unprotected_file_patterns": ""
  }'

echo "Branch protection configured."

# Verify
echo ""
echo "=== Verification ==="
curl -s "${FORGEJO_URL}/api/v1/repos/${OWNER}/${REPO}/branch_protections" \
  -H "Authorization: token ${FORGEJO_PAT}" | jq '.[] | select(.branch_name == "'"${BRANCH}"'") | {
    branch_name,
    required_approvals,
    enable_status_check,
    status_check_contexts,
    dismiss_stale_approvals,
    block_on_rejected_reviews,
    enable_push_whitelist,
    push_whitelist_usernames,
    enable_merge_whitelist,
    merge_whitelist_usernames
  }'
```
