Add push/merge/approvals whitelist params so an admin can allow a specific user (e.g. a release bot) to push to an otherwise locked branch: - enable_push_whitelist / push_whitelist_usernames - enable_merge_whitelist / merge_whitelist_usernames - enable_approvals_whitelist / approvals_whitelist_usernames Username lists are comma-separated and replace the existing list. PATCH null-safety extends to them: an unpassed list serializes as null (leave-unchanged), never [] (which would wipe an existing whitelist). Covered by TestEditBranchProtectionFn_PushWhitelistRoundTrip. Update the co-located branch-protection showboat demo and link it from demos/README.md (new "Branch protection (governance)" section).
132 lines
7.2 KiB
Markdown
132 lines
7.2 KiB
Markdown
# Branch protection management (uc6)
|
|
|
|
*2026-06-02T14:30:58Z by Showboat 0.6.1*
|
|
<!-- showboat-id: 7b514f84-82f9-4e11-a050-68eb72df0e9d -->
|
|
|
|
*Captured: 2026-06-02 via Showboat 0.6.1*
|
|
<!-- captured-for: PR #196 -->
|
|
<!-- captured-at: 2026-06-02 -->
|
|
<!-- captured-against: 0826172 (feat/branch-protection-impl-uc6) -->
|
|
<!-- updated-for: push/merge/approvals whitelist params on edit_branch_protection (feat/bp-whitelist-params) -->
|
|
<!-- updated-at: 2026-06-02 -->
|
|
|
|
Proves the `branch-protection` capability — spec [`spec.md`](./spec.md) (archived change `2026-06-02-branch-protection-management`), issue `forgejo-mcp-uc6` (discovered from `forgejo-mcp-f6h`: a repo with no protection let Renovate automerge before CI was green).
|
|
|
|
> **Token-free demo.** Reading or writing real branch protection requires a repo-admin token, and the server, like any client, must not leak it. This demo deliberately uses **no token and no live instance**: it proves the surface through the CLI tool registry and the validation path, and proves behaviour through the test suite, which exercises the exact Forgejo HTTP round-trips against an in-process `httptest` server. No secrets appear anywhere in this file.
|
|
|
|
## Replay setup
|
|
|
|
```bash
|
|
export FORGEJO_MCP_BIN="${FORGEJO_MCP_BIN:-./forgejo-mcp}" # local build of this branch
|
|
# All commands below run from the repo root and need NO token / NO network.
|
|
```
|
|
|
|
## The five tools are registered
|
|
|
|
The server exposes the branch-protection CRUD tools (token-free introspection via the CLI tool registry).
|
|
|
|
```bash
|
|
"${FORGEJO_MCP_BIN:-./forgejo-mcp}" --cli list 2>/dev/null | grep branch_protection
|
|
```
|
|
|
|
```output
|
|
create_branch_protection Create a branch protection rule (e.g. require status checks before merge)
|
|
delete_branch_protection Delete a branch protection rule by name
|
|
edit_branch_protection Edit a branch protection rule. Only fields you pass are changed; omitted fields are left untouched.
|
|
get_branch_protection Get a single branch protection rule by name
|
|
list_branch_protections List a repository's branch protection rules (bounded by page/limit)
|
|
```
|
|
|
|
## Scenario: Create requires a branch name
|
|
|
|
Spec: *create_branch_protection ... SHALL return an error result and SHALL NOT call Forgejo* when `branch_name` is missing. The guard runs before any client/network call, so this is reproducible with no token and no URL.
|
|
|
|
```bash
|
|
"${FORGEJO_MCP_BIN:-./forgejo-mcp}" -debug=false --cli create_branch_protection --args '{"owner":"goern","repo":"forgejo-mcp"}' 2>&1 | grep -o "branch_name is required" | head -1
|
|
```
|
|
|
|
```output
|
|
branch_name is required
|
|
```
|
|
|
|
## Scenario: Whitelist a user to push to a protected branch
|
|
|
|
`edit_branch_protection` accepts push/merge/approvals whitelist params so an
|
|
admin can allow a specific user (e.g. a release bot like `op1st-gitops`) to
|
|
push to an otherwise locked branch. Usernames are comma-separated and **replace**
|
|
the existing list; each user must already be a collaborator with write access —
|
|
the whitelist filters *which writers* may push, it does not grant access.
|
|
|
|
```bash
|
|
"${FORGEJO_MCP_BIN:-./forgejo-mcp}" --cli list 2>/dev/null \
|
|
| grep edit_branch_protection
|
|
```
|
|
|
|
```output
|
|
edit_branch_protection Edit a branch protection rule. Only fields you pass are changed; omitted fields are left untouched.
|
|
```
|
|
|
|
PATCH null-safety extends to the whitelists: an unpassed list serializes as
|
|
`null` (leave-unchanged), never `[]` (which would silently wipe an existing
|
|
whitelist). This is asserted by `TestEditBranchProtectionFn_PushWhitelistRoundTrip`
|
|
below, which sends `enable_push_whitelist: true` + `push_whitelist_usernames:
|
|
["goern","op1st-gitops"]` and verifies the unpassed merge whitelist stays `null`.
|
|
|
|
## Scenario coverage via the executable spec
|
|
|
|
Each spec scenario is a test against an in-process `httptest` Forgejo (real request/response, no network, no token). The names map 1:1 to the spec: `status_check_contexts` round-trip, edit PATCH null-safety, push-whitelist round-trip, list/collection bounding + truncation sentinel, get/collection 404 → resource error, single-resource happy path, malformed URI → invalid-params, and the slash-glob rule URI.
|
|
|
|
```bash
|
|
go test -v -run "BranchProtection|ParseBranchProtection|SplitContexts" ./operation/branchprotection/ ./operation/resource/ 2>&1 | grep -E "^(=== RUN|--- PASS|--- FAIL|PASS|FAIL|ok)" | sed -E "s#\t# #g"
|
|
```
|
|
|
|
```output
|
|
=== RUN TestListBranchProtectionsFn
|
|
--- PASS: TestListBranchProtectionsFn (0.00s)
|
|
=== RUN TestGetBranchProtectionFn_OK
|
|
--- PASS: TestGetBranchProtectionFn_OK (0.00s)
|
|
=== RUN TestGetBranchProtectionFn_NotFound
|
|
--- PASS: TestGetBranchProtectionFn_NotFound (0.00s)
|
|
=== RUN TestCreateBranchProtectionFn_StatusCheckRoundTrip
|
|
--- PASS: TestCreateBranchProtectionFn_StatusCheckRoundTrip (0.00s)
|
|
=== RUN TestCreateBranchProtectionFn_MissingBranchName
|
|
--- PASS: TestCreateBranchProtectionFn_MissingBranchName (0.00s)
|
|
=== RUN TestEditBranchProtectionFn_OnlyPassedFields
|
|
--- PASS: TestEditBranchProtectionFn_OnlyPassedFields (0.00s)
|
|
=== RUN TestEditBranchProtectionFn_ContextsRoundTrip
|
|
--- PASS: TestEditBranchProtectionFn_ContextsRoundTrip (0.00s)
|
|
=== RUN TestEditBranchProtectionFn_PushWhitelistRoundTrip
|
|
--- PASS: TestEditBranchProtectionFn_PushWhitelistRoundTrip (0.00s)
|
|
=== RUN TestDeleteBranchProtectionFn_OK
|
|
--- PASS: TestDeleteBranchProtectionFn_OK (0.00s)
|
|
=== RUN TestSplitContexts
|
|
--- PASS: TestSplitContexts (0.00s)
|
|
=== RUN TestBranchProtectionsResource_HappyPath
|
|
--- PASS: TestBranchProtectionsResource_HappyPath (0.00s)
|
|
=== RUN TestBranchProtectionsResource_Truncation
|
|
--- PASS: TestBranchProtectionsResource_Truncation (0.00s)
|
|
=== RUN TestBranchProtectionsResource_NotFound
|
|
--- PASS: TestBranchProtectionsResource_NotFound (0.00s)
|
|
=== RUN TestBranchProtectionResource_HappyPath
|
|
--- PASS: TestBranchProtectionResource_HappyPath (0.00s)
|
|
=== RUN TestBranchProtectionResource_MalformedURI
|
|
--- PASS: TestBranchProtectionResource_MalformedURI (0.00s)
|
|
PASS
|
|
ok codeberg.org/goern/forgejo-mcp/v2/operation/branchprotection (cached)
|
|
=== RUN TestParseBranchProtections
|
|
--- PASS: TestParseBranchProtections (0.00s)
|
|
=== RUN TestParseBranchProtections_Invalid
|
|
--- PASS: TestParseBranchProtections_Invalid (0.00s)
|
|
=== RUN TestParseBranchProtection
|
|
--- PASS: TestParseBranchProtection (0.00s)
|
|
=== RUN TestParseBranchProtection_GlobRuleWithSlash
|
|
--- PASS: TestParseBranchProtection_GlobRuleWithSlash (0.00s)
|
|
=== RUN TestParseBranchProtection_Invalid
|
|
--- PASS: TestParseBranchProtection_Invalid (0.00s)
|
|
PASS
|
|
ok codeberg.org/goern/forgejo-mcp/v2/operation/resource (cached)
|
|
```
|
|
|
|
## What this demo does not show (by design)
|
|
|
|
A live `create_branch_protection` / `list_branch_protections` against a real repo is omitted: it needs a repo-admin token, and showing it risks leaking that token in CLI debug output. The `httptest` suite above drives the identical SDK calls and asserts the exact request bodies (e.g. `enable_status_check: true` + the `status_check_contexts` list) and responses, so the contract is proven without a secret. To run it live yourself: `forgejo-mcp --cli list_branch_protections --args '{"owner":"...","repo":"..."}'` with `-url`/`-token` set in your shell — never on the command line of a shared session.
|