markdown · 4911 bytes Raw Blame History

Rulesets

Read-only rulesets surface. Mirrors GitHub's /repos/{owner}/{repo}/rulesets response shape. shithub synthesizes ruleset rows from the branch_protection_rules table — one branch-protection row becomes one ruleset, named after its pattern, with each configured field projected as a typed rule object.

All endpoints require Authorization: Bearer <pat> with repo:read and gate on ActionRepoRead. The common API conventions apply.

Ruleset shape

{
  "id": 17,
  "name": "Pattern: main",
  "target": "branch",
  "source_type": "Repository",
  "source": "alice/demo",
  "enforcement": "active",
  "conditions": {
    "ref_name": {
      "include": ["refs/heads/main"],
      "exclude": []
    }
  },
  "rules": [
    {
      "type": "pull_request",
      "parameters": {
        "required_approving_review_count": 1,
        "dismiss_stale_reviews_on_push": false,
        "require_code_owner_review": true
      }
    },
    {"type": "non_fast_forward"},
    {"type": "deletion"},
    {"type": "required_signatures"},
    {
      "type": "required_status_checks",
      "parameters": {
        "required_status_checks": [{"context": "ci/build"}],
        "strict_required_status_checks_policy": false
      }
    }
  ],
  "created_at": "2026-05-12T04:00:00Z",
  "updated_at": "2026-05-13T04:00:00Z"
}

Rule types

Each rules[].type maps to one or more columns on the underlying protection row:

type Columns Notes
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.
non_fast_forward prevent_force_push Force-push gate. No parameters.
deletion prevent_deletion Branch-delete gate. No parameters.
required_signatures require_signed_commits Verified-signature gate. No parameters.
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).

Rules an admin hasn't configured are absent from the array — clients should treat missing as "rule not configured" rather than assuming defaults.

Field notes

  • target — always "branch". Tag rulesets ship in a future sprint when tag protection lands.
  • source_type / source — always "Repository" / "<owner>/<repo>". Org-scoped and enterprise-scoped rulesets are out of scope until shithub grows enterprise-tier accounts.
  • enforcement — always "active". We don't model "disabled" or "evaluate" modes; every row in the table is enforced by the pre-receive hook.
  • 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.
  • bypass_actors — not emitted. Bypass / multi-actor exemptions ship in a future sprint.

List rulesets

GET /api/v1/repos/{owner}/{repo}/rulesets

Required scope: repo:read.

Returns every ruleset on the repo, ordered by id. Empty repos and repos with no configured protection rules return [].

Get a single ruleset

GET /api/v1/repos/{owner}/{repo}/rulesets/{id}

Required scope: repo:read.

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.

Rules applying to a branch

GET /api/v1/repos/{owner}/{repo}/rules/branches/{branch}

Required scope: repo:read.

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.

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.

Errors

Status When
401 PAT missing/invalid/expired/revoked.
403 PAT lacks repo:read scope.
404 Repo not found, ruleset id not found, or cross-repo lookup.

Mutating rulesets

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.

View source
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`.