@@ -0,0 +1,120 @@ |
| | 1 | +# Rulesets |
| | 2 | + |
| | 3 | +Read-only rulesets surface. Mirrors GitHub's |
| | 4 | +[`/repos/{owner}/{repo}/rulesets`](https://docs.github.com/en/rest/repos/rules) |
| | 5 | +response shape. shithub synthesizes ruleset rows from the |
| | 6 | +`branch_protection_rules` table — one branch-protection row |
| | 7 | +becomes one ruleset, named after its pattern, with each |
| | 8 | +configured field projected as a typed rule object. |
| | 9 | + |
| | 10 | +All endpoints require `Authorization: Bearer <pat>` with |
| | 11 | +`repo:read` and gate on `ActionRepoRead`. The |
| | 12 | +[common API conventions](overview.md) apply. |
| | 13 | + |
| | 14 | +## Ruleset shape |
| | 15 | + |
| | 16 | +```json |
| | 17 | +{ |
| | 18 | + "id": 17, |
| | 19 | + "name": "Pattern: main", |
| | 20 | + "target": "branch", |
| | 21 | + "source_type": "Repository", |
| | 22 | + "source": "alice/demo", |
| | 23 | + "enforcement": "active", |
| | 24 | + "conditions": { |
| | 25 | + "ref_name": { |
| | 26 | + "include": ["refs/heads/main"], |
| | 27 | + "exclude": [] |
| | 28 | + } |
| | 29 | + }, |
| | 30 | + "rules": [ |
| | 31 | + { |
| | 32 | + "type": "pull_request", |
| | 33 | + "parameters": { |
| | 34 | + "required_approving_review_count": 1, |
| | 35 | + "dismiss_stale_reviews_on_push": false, |
| | 36 | + "require_code_owner_review": true |
| | 37 | + } |
| | 38 | + }, |
| | 39 | + {"type": "non_fast_forward"}, |
| | 40 | + {"type": "deletion"}, |
| | 41 | + {"type": "required_signatures"}, |
| | 42 | + { |
| | 43 | + "type": "required_status_checks", |
| | 44 | + "parameters": { |
| | 45 | + "required_status_checks": [{"context": "ci/build"}], |
| | 46 | + "strict_required_status_checks_policy": false |
| | 47 | + } |
| | 48 | + } |
| | 49 | + ], |
| | 50 | + "created_at": "2026-05-12T04:00:00Z", |
| | 51 | + "updated_at": "2026-05-13T04:00:00Z" |
| | 52 | +} |
| | 53 | +``` |
| | 54 | + |
| | 55 | +### Rule types |
| | 56 | + |
| | 57 | +Each `rules[].type` maps to one or more columns on the underlying |
| | 58 | +protection row: |
| | 59 | + |
| | 60 | +| `type` | Columns | Notes | |
| | 61 | +|--------|---------|-------| |
| | 62 | +| `pull_request` | `require_pr_for_push`, `required_review_count`, `require_code_owner_review`, `dismiss_stale_reviews_on_push` | Emitted when any of these is non-default. `parameters.required_approving_review_count` is the headline value. | |
| | 63 | +| `non_fast_forward` | `prevent_force_push` | Force-push gate. No parameters. | |
| | 64 | +| `deletion` | `prevent_deletion` | Branch-delete gate. No parameters. | |
| | 65 | +| `required_signatures` | `require_signed_commits` | Verified-signature gate. No parameters. | |
| | 66 | +| `required_status_checks` | `status_checks_required`, `dismiss_stale_status_checks_on_push` | `parameters.required_status_checks` is an array of `{context}` objects. `integration_id` is omitted (we don't track per-app integrations). | |
| | 67 | + |
| | 68 | +Rules an admin hasn't configured are absent from the array — clients should treat missing as "rule not configured" rather than assuming defaults. |
| | 69 | + |
| | 70 | +### Field notes |
| | 71 | + |
| | 72 | +- `target` — always `"branch"`. Tag rulesets ship in a future sprint when tag protection lands. |
| | 73 | +- `source_type` / `source` — always `"Repository"` / `"<owner>/<repo>"`. Org-scoped and enterprise-scoped rulesets are out of scope until shithub grows enterprise-tier accounts. |
| | 74 | +- `enforcement` — always `"active"`. We don't model `"disabled"` or `"evaluate"` modes; every row in the table is enforced by the pre-receive hook. |
| | 75 | +- `conditions.ref_name.include` — single-entry array with the pattern prefixed by `refs/heads/`. shithub's patterns use `filepath.Match` semantics (`*` doesn't cross `/`), not gh's fnmatch. |
| | 76 | +- `bypass_actors` — not emitted. Bypass / multi-actor exemptions ship in a future sprint. |
| | 77 | + |
| | 78 | +## List rulesets |
| | 79 | + |
| | 80 | +``` |
| | 81 | +GET /api/v1/repos/{owner}/{repo}/rulesets |
| | 82 | +``` |
| | 83 | + |
| | 84 | +Required scope: `repo:read`. |
| | 85 | + |
| | 86 | +Returns every ruleset on the repo, ordered by id. Empty repos and repos with no configured protection rules return `[]`. |
| | 87 | + |
| | 88 | +## Get a single ruleset |
| | 89 | + |
| | 90 | +``` |
| | 91 | +GET /api/v1/repos/{owner}/{repo}/rulesets/{id} |
| | 92 | +``` |
| | 93 | + |
| | 94 | +Required scope: `repo:read`. |
| | 95 | + |
| | 96 | +Returns the single ruleset whose id matches. 404 when the id doesn't belong to this repo — same status as "doesn't exist" so the response doesn't leak existence across repo boundaries. |
| | 97 | + |
| | 98 | +## Rules applying to a branch |
| | 99 | + |
| | 100 | +``` |
| | 101 | +GET /api/v1/repos/{owner}/{repo}/rules/branches/{branch} |
| | 102 | +``` |
| | 103 | + |
| | 104 | +Required scope: `repo:read`. |
| | 105 | + |
| | 106 | +Returns every ruleset whose pattern matches the given branch name. **All** matching patterns are returned, not just the longest-match — the longest-match heuristic shithub's pre-receive enforcer uses is an internal precedence detail, not a contract surface. |
| | 107 | + |
| | 108 | +Branch names that contain `/` (`feature/x`, `release/v1.0`) are matched against patterns using `filepath.Match`: `*` does not cross path separators, so `release/*` matches `release/v1.0` but not `release/v1.0/sub`. |
| | 109 | + |
| | 110 | +## Errors |
| | 111 | + |
| | 112 | +| Status | When | |
| | 113 | +|-------:|-------------------------------------------------| |
| | 114 | +| 401 | PAT missing/invalid/expired/revoked. | |
| | 115 | +| 403 | PAT lacks `repo:read` scope. | |
| | 116 | +| 404 | Repo not found, ruleset id not found, or cross-repo lookup. | |
| | 117 | + |
| | 118 | +## Mutating rulesets |
| | 119 | + |
| | 120 | +Creating, updating, and deleting rulesets via REST is not yet shipped. Use the web UI at `Settings → Branches → Add rule` for now. The shape of the future POST/PATCH/DELETE endpoints will mirror gh's documented surface and will require `repo:write` + `ActionRepoAdmin`. |