layered-soul/skills/forgejo-operations/references/branch-protection-api.md
Hermes & Sam 5c5df32101 Populate layered-soul: identity, memories, skills, plan (Hermes & Sam)
- SOUL.md: full agent identity, operating principles, voice
- IDENTITY.md: runtime identity, hosts, boundaries
- USER.md: operator context imported from hermes-soul
- AGENTS.md: actual operating rules, infrastructure, quick reference
- memories/curated/: 5 topics (tailscale, forgejo, agents, projects, vaultwarden)
- skills/: 9 cross-harness skills imported from hermes-soul after review
- docs/PLAN-CONFIGURE-PRIVATE-REPO.md: configuration plan
- Validate: passes clean
2026-06-14 00:21:26 +02:00

90 lines
2.8 KiB
Markdown

# Forgejo Branch Protection API
For history rewrites (flattening, squashing, filter-branch) when main is
protected, you must temporarily remove branch protection, force push, then
re-create it. The Forgejo API identifies protections by branch name, not by ID.
## Prerequisites
- Token with `write:organization` scope (branch protection management)
- Token stored in `~/.hermes/.env` as `FORGEJO_API_TOKEN`
```bash
source ~/.hermes/.env 2>/dev/null
API="https://code.smilepowered.org/api/v1/repos/<owner>/<repo>"
```
## List Current Protections
```bash
curl -s -H "Authorization: token $FORGEJO_API_TOKEN" \
"$API/branch_protections" | python3 -m json.tool
```
Returns an array of protection objects. Each has `branch_name`, `rule_name`,
`enable_push` (false = protected), and other fields.
## Remove Protection (temporarily)
```bash
# DELETE by branch name — returns HTTP 204 on success
curl -s -X DELETE -H "Authorization: token $FORGEJO_API_TOKEN" \
"$API/branch_protections/main" -w "\nHTTP %{http_code}\n"
```
Only 204 means success. Any other code = protection still active.
## Force Push (while unprotected)
```bash
git push --force-with-lease forgejo main
```
Use `--force-with-lease` not `--force` — it refuses if someone pushed since
your last fetch.
## Re-Create Protection (immediately after push)
```bash
curl -s -X POST -H "Authorization: token $FORGEJO_API_TOKEN" \
-H "Content-Type: application/json" \
"$API/branch_protections" \
-d '{
"branch_name": "main",
"rule_name": "main",
"enable_push": false,
"enable_push_whitelist": false
}' -w "\nHTTP %{http_code}\n"
```
HTTP 201 = created. Match the original protection settings — the above is the
minimal "no direct push, no whitelist" config used by Clawdie repos.
## Verification
```bash
curl -s -H "Authorization: token $FORGEJO_API_TOKEN" \
"$API/branch_protections" | python3 -c "
import sys, json
for p in json.load(sys.stdin):
if p['branch_name'] == 'main':
print(f'main: enable_push={p[\"enable_push\"]}')
"
```
Should show `enable_push=False` (protected).
## Pitfalls
- **Forgejo IDs protections by branch name, not numeric ID.** The GET response
has no `id` field. Use the branch name as the DELETE key.
- **Must re-create immediately.** The window between DELETE and re-POST is an
unprotected window — anyone can push directly. Keep it as short as possible.
On a fast connection: < 2 seconds.
- **Token scope matters.** `write:repository` is NOT enough for branch
protection API you need `write:organization`. The day-to-day agent token
(`write:repository` only) cannot do this. Use a token with org scope or
temporarily re-scope.
- **Matched settings on re-create.** Copy the original protection object fields
exactly. If you lose settings like `required_approvals` or
`enable_status_check`, the repo is under-protected until fixed.