- 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
90 lines
2.8 KiB
Markdown
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.
|