Vetrix Docs

Protected Branch Rules

Branch protection rules enforce code review and CI requirements before changes can land on important branches. They are configured per-repository via the API and evaluated at merge time.


Rule Fields

Field Type Description
pattern string Glob matched against branch names, e.g. main, release/*
require_reviews int Minimum approving reviews before merge is permitted (0 = none)
dismiss_stale_reviews bool Dismiss existing approvals when new commits are pushed
require_status_checks string[] CI context names that must pass before merge, e.g. ["ci/test"]
restrict_pushes bool When true, only repository admins may push directly

API Reference

List Rules

GET /api/v1/repos/{owner}/{repo}/branches/protection
Authorization: Bearer <token>

Response (200)

[
  {
    "id": "...",
    "repo_id": "...",
    "pattern": "main",
    "require_reviews": 2,
    "dismiss_stale_reviews": true,
    "require_status_checks": ["ci/test", "ci/lint"],
    "restrict_pushes": true,
    "created_at": "2026-04-10T00:00:00Z",
    "updated_at": "2026-04-10T00:00:00Z"
  }
]

Create or Update a Rule

PUT /api/v1/repos/{owner}/{repo}/branches/protection/{pattern}
Authorization: Bearer <token>
Content-Type: application/json

The {pattern} path segment identifies the rule. If a rule for that pattern already exists it is updated; otherwise a new rule is created.

Request body

{
  "require_reviews": 2,
  "dismiss_stale_reviews": true,
  "require_status_checks": ["ci/test"],
  "restrict_pushes": false
}

Response (200)

{
  "id": "...",
  "repo_id": "...",
  "pattern": "release/*",
  "require_reviews": 2,
  ...
}

Only the repository owner or a server admin may create or modify rules.


Delete a Rule

DELETE /api/v1/repos/{owner}/{repo}/branches/protection/{pattern}
Authorization: Bearer <token>

Returns 204 No Content on success.


Pattern Syntax

Patterns use Go filepath.Match glob semantics:

Pattern Matches Does not match
main main main-old, develop
release/* release/1.0, release/2.0.0 release/1.0/patch
feature-* feature-login, feature-signup bugfix-login
v[0-9]* v1, v2.0, v10 va.0, alpha

Note: * does not match the / separator. Use release/* not release/** for a single path segment.


How Rules Are Enforced

Rules are checked at merge time via the API (POST .../pulls/{n}/merge). The merge is rejected if:

  1. The target branch matches the rule pattern, and
  2. The number of approving reviews is below require_reviews, or
  3. Any context listed in require_status_checks has not passed.

Direct Push Restriction

When restrict_pushes is true, the git pre-receive hook (Feature 01) checks whether the pushing user is a repository admin. Non-admin direct pushes to matching branches are rejected at the protocol level.


Example Configurations

Protect main — require two reviews and CI

PUT /api/v1/repos/alice/myrepo/branches/protection/main

{
  "require_reviews": 2,
  "dismiss_stale_reviews": true,
  "require_status_checks": ["ci/test", "ci/coverage"],
  "restrict_pushes": true
}

Protect all release branches — require one review

PUT /api/v1/repos/alice/myrepo/branches/protection/release%2F*

{
  "require_reviews": 1,
  "dismiss_stale_reviews": false,
  "require_status_checks": ["ci/test"],
  "restrict_pushes": false
}

URL-encode / as %2F in the path segment when the pattern contains slashes.


Relationship to Feature 01 protected_branches

Feature 01 introduced a basic protected_branches table used exclusively by the git pre-receive hook to determine whether a direct push is allowed. Feature 02 adds the richer protected_branch_rules table exposed via this API, which adds review-count gates and status-check requirements evaluated at merge time.

The two tables are independent; you can configure both or only one depending on how strictly you want to enforce policy.


Required-pipeline gate (require_pipeline)

The required-pipeline gate makes a branch refuse changes until the relevant commit has a passing CI pipeline. It is configured on the simple protected-branch model (the protected_branches table the git push / merge / MR-open paths read), via its own API:

GET /api/v1/repos/{owner}/{repo}/protected-branches
PUT /api/v1/repos/{owner}/{repo}/protected-branches/{pattern}

This is a different surface from the /branches/protection/* Feature-02 API documented above. The Feature-02 require_status_checks field is separate and is not what drives this gate.

Rule fields (simple model)

Field Type Default Description
pattern string Glob matched against branch names (path segment of the PUT URL)
restrict_push bool false When true, only repo admins / push-allowlist members may push directly
required_reviews int 0 Minimum approving reviews before merge
require_pipeline bool false When true, enable the required-pipeline gate on this branch

Enabling the gate

PUT the rule with require_pipeline: true (repo-admin permission required):

PUT /api/v1/repos/alice/myrepo/protected-branches/main

{
  "restrict_push": false,
  "required_reviews": 0,
  "require_pipeline": true
}

The {pattern} path segment may contain slashes directly (e.g. protected-branches/release/*) or percent-encoded (release%2F*).

Verdict: soft vs. hard

The gate classifies the latest terminal pipeline for the commit it is checking into one of five verdicts:

Verdict Meaning
success No failed or cancelled jobs.
soft_only Every failure is a tolerated allow_failure job; no hard failures. Treated as a pass.
hard_failure At least one non-tolerated failed job, or any cancelled job.
pending A pipeline exists but the latest one has not reached a terminal state.
missing No pipeline exists for the commit at all.

The verdict is recomputed live from current job state on every check, so retrying a pipeline immediately reflects the new outcome.

What the gate does on each surface

The gate is enforced on three surfaces, each with deliberately different strictness:

Surface Allowed verdicts Blocked verdicts On block
Merge success, soft_only hard_failure, pending, missing 409 Conflict with a machine-readable reason (pipeline_hard_failure / pipeline_pending / pipeline_missing)
MR-open success, soft_only, pending, missing hard_failure only 409 Conflict (pipeline_hard_failure)
Push every verdict except a hard_failure tip hard_failure (on the pushed tip) push rejected at the git protocol with a remote message

Notes:

  • Merge is the strict surface. A merge into a gated branch is only allowed once CI has actually finished and passed (or soft-only-failed). pending and missing both block the merge.
  • MR-open is the lenient surface. You can open a merge request toward a gated branch while CI is still running (or before any pipeline exists) — pending and missing are allowed. Only a confirmed hard_failure blocks the open.
  • Push fails open. The push gate only rejects when the pushed tip already has a hard_failure verdict — which is rare in practice, because a push is usually what triggers the pipeline in the first place. A push that has no pipeline yet (missing) or one still running (pending) is allowed through so CI can start. Repository admins and push-allowlist members bypass the push gate entirely.

Independent of restrict_push

require_pipeline and restrict_push are independent toggles. A rule with require_pipeline: true, restrict_push: false gates merges / MR-opens on CI and rejects a push whose tip is a hard failure, but does not otherwise restrict direct pushes — a normal non-admin push that advances the branch (and triggers a new pipeline) is still allowed. Set restrict_push: true separately if you also want to forbid non-admin direct pushes.