GitHub Rulesets¶
xfg can manage GitHub Rulesets declaratively using the sync command. Define rulesets in your config file, and xfg will create, update, or delete them to match your desired state.
GitHub-Only Feature
Rulesets are only available for GitHub repositories. Azure DevOps and GitLab repos will be skipped when running xfg sync.
Quick Start¶
id: my-config
files:
.prettierrc.json:
content:
semi: false
settings:
rulesets:
main-protection:
target: branch
enforcement: active
conditions:
refName:
include:
- refs/heads/main
rules:
- type: pull_request
parameters:
requiredApprovingReviewCount: 1
repos:
- git: git@github.com:your-org/your-repo.git
Why Rulesets?¶
GitHub Rulesets offer advantages over legacy branch protection rules:
- Pattern-based conditions - Apply rules to multiple branches with glob patterns
- Multiple rules per ruleset - Group related rules together
- Bypass actors - Fine-grained control over who can bypass rules
- Evaluate mode - Test rules without enforcing them
- Advanced rules - Code scanning, workflows, file restrictions
Ruleset Structure¶
settings:
rulesets:
ruleset-name: # Unique name for this ruleset
target: branch # "branch" or "tag"
enforcement: active # "active", "disabled", or "evaluate"
bypassActors: # Optional: who can bypass these rules
- actorId: 12345
actorType: Team # "Team", "User", or "Integration"
bypassMode: always # "always" or "pull_request"
conditions: # Which refs this applies to
refName:
include:
- refs/heads/main
- refs/heads/release/*
exclude:
- refs/heads/dev*
rules: # Array of rule objects
- type: pull_request
parameters:
requiredApprovingReviewCount: 2
Available Rule Types¶
Pull Request Rules¶
- type: pull_request
parameters:
requiredApprovingReviewCount: 2 # 0-10
dismissStaleReviewsOnPush: true
requireCodeOwnerReview: true
requireLastPushApproval: true
requiredReviewThreadResolution: true
allowedMergeMethods:
- squash
- rebase
requiredReviewers: # (beta) file-pattern-based reviewers
- filePatterns: ["src/auth/**"]
minimumApprovals: 2
reviewer:
id: 123456
type: Team
Status Checks¶
- type: required_status_checks
parameters:
strictRequiredStatusChecksPolicy: true
doNotEnforceOnCreate: false
requiredStatusChecks:
- context: "ci/build"
- context: "ci/test"
integrationId: 12345
Simple Rules (No Parameters)¶
- type: required_signatures
- type: required_linear_history
- type: non_fast_forward
- type: creation
- type: deletion
Update Rule¶
Deployments¶
Code Scanning¶
- type: code_scanning
parameters:
codeScanningTools:
- tool: CodeQL
alertsThreshold: errors # none, errors, errors_and_warnings, all
securityAlertsThreshold: critical # none, critical, high_or_higher, medium_or_higher, all
Code Quality¶
Workflows¶
Require specific GitHub Actions workflows to pass:
- type: workflows
parameters:
doNotEnforceOnCreate: false
workflows:
- path: .github/workflows/ci.yml
repositoryId: 123456789
ref: refs/heads/main
Pattern Rules¶
All pattern rules support the same parameters:
| Parameter | Type | Description |
|---|---|---|
name |
string | Display name for the rule (optional) |
operator |
string | starts_with, ends_with, contains, or regex |
pattern |
string | The pattern to match |
negate |
boolean | If true, the rule applies when the pattern does NOT match |
- type: commit_message_pattern
parameters:
name: "Conventional commits"
operator: regex
pattern: "^(feat|fix|docs|style|refactor|test|chore)(\\(.+\\))?: .+"
negate: false
- type: commit_author_email_pattern
parameters:
name: "Corporate email only"
operator: ends_with
pattern: "@your-company.com"
- type: committer_email_pattern
parameters:
operator: ends_with
pattern: "@your-company.com"
- type: branch_name_pattern
parameters:
operator: regex
pattern: "^(feature|bugfix|hotfix)/.+"
- type: tag_name_pattern
parameters:
name: "Semantic versioning"
operator: regex
pattern: "^v[0-9]+\\.[0-9]+\\.[0-9]+"
File Restrictions¶
- type: file_path_restriction
parameters:
restrictedFilePaths:
- ".github/workflows/*"
- "package-lock.json"
- type: file_extension_restriction
parameters:
restrictedFileExtensions:
- ".exe"
- ".dll"
- ".jar"
- type: max_file_path_length
parameters:
maxFilePathLength: 255
- type: max_file_size
parameters:
maxFileSize: 10485760 # 10MB in bytes
Inheritance and Opt-Out¶
Rulesets from conditional groups merge after explicit group rulesets and before repo overrides. Per-repo inherit: false discards all inherited rulesets including those from conditional groups.
Like files, rulesets support inheritance with options to opt out.
Default Inheritance¶
Define defaults at the root level and override per-repo:
# Root-level defaults for all repos
settings:
rulesets:
main-protection:
target: branch
enforcement: active
conditions:
refName:
include: [refs/heads/main]
rules:
- type: pull_request
parameters:
requiredApprovingReviewCount: 1
repos:
# Gets default ruleset
- git: git@github.com:your-org/standard-repo.git
# Overrides with stricter requirements
- git: git@github.com:your-org/critical-repo.git
settings:
rulesets:
main-protection:
rules:
- type: pull_request
parameters:
requiredApprovingReviewCount: 3 # Override
Single Ruleset Opt-Out¶
Set a ruleset to false to exclude it from a specific repo:
settings:
rulesets:
main-protection:
target: branch
enforcement: active
release-protection:
target: branch
enforcement: active
repos:
# Gets both rulesets
- git: git@github.com:your-org/standard-repo.git
# Skips release-protection only
- git: git@github.com:your-org/no-releases.git
settings:
rulesets:
release-protection: false
Skipping All Inherited Rulesets¶
Use inherit: false to skip all root-level rulesets. You can optionally add repo-specific rulesets:
settings:
rulesets:
main-protection:
target: branch
enforcement: active
repos:
# No rulesets at all
- git: git@github.com:your-org/experimental.git
settings:
rulesets:
inherit: false
# Skip inherited, add custom
- git: git@github.com:your-org/custom-rules.git
settings:
rulesets:
inherit: false
custom-ruleset:
target: tag
enforcement: active
conditions:
refName:
include: [refs/tags/v*]
rules:
- type: required_signatures
Appending to Arrays¶
By default, per-repo arrays (like bypassActors and rules) replace inherited arrays entirely. Use the $arrayMerge directive to append or prepend instead:
settings:
rulesets:
main-protection:
target: branch
enforcement: active
bypassActors:
- actorId: 2740 # Renovate — shared
actorType: Integration
bypassMode: always
rules:
- type: pull_request
parameters:
requiredApprovingReviewCount: 1
repos:
# Add a repo-specific bypass actor without losing Renovate
- git: git@github.com:your-org/special-repo.git
settings:
rulesets:
main-protection:
bypassActors:
$arrayMerge: append
$values:
- actorId: 123456
actorType: Team
bypassMode: pull_request
rules:
$arrayMerge: append
$values:
- type: required_status_checks
parameters:
requiredStatusChecks:
- context: "ci/build"
Result for special-repo: bypassActors has both Renovate and the team; rules has both pull_request and required_status_checks.
Available strategies: append (add after), prepend (add before), replace (same as default). See Merge Strategies for more details.
Bypass Actors¶
Allow specific users, teams, or integrations to bypass rules:
bypassActors:
# GitHub App (e.g., Renovate, Dependabot)
- actorId: 2719952
actorType: Integration
bypassMode: always
# Team
- actorId: 123456
actorType: Team
bypassMode: pull_request # Only bypass via PRs
# User
- actorId: 789012
actorType: User
bypassMode: always
Finding Actor IDs
Use the GitHub API to find actor IDs.
# Team ID
gh api orgs/{org}/teams/{team-slug} --jq '.id'
# User ID
gh api users/{username} --jq '.id'
# Integration ID (GitHub Apps)
gh api orgs/{org}/installations --jq '.installations[] | {name: .app_slug, id: .app_id}'
Orphan Deletion¶
When deleteOrphaned: true is set, xfg tracks which rulesets it manages and deletes any that are removed from the config:
id: my-config
deleteOrphaned: true
settings:
rulesets:
main-protection: # This ruleset is tracked
# ...
If you later remove main-protection from the config and run xfg sync, it will be deleted from the repository.
Use --no-delete to skip orphan deletion:
Dry Run¶
Preview changes without applying them:
Output shows planned changes:
Loading config from: ./config.yaml
Running in DRY RUN mode - no changes will be made
Found 2 repositories with rulesets
[1/2] your-org/repo1: Processing rulesets...
[1/2] ✓ your-org/repo1: [DRY RUN] 1 created, 0 updated, 0 deleted
[2/2] your-org/repo2: Processing rulesets...
[2/2] ✓ your-org/repo2: [DRY RUN] 0 created, 1 updated, 0 deleted
Combining with File Sync¶
File sync and settings are handled together by a single command:
# Sync files and apply rulesets in one run
xfg sync -c config.yaml
# Preview first, then apply
xfg sync -c config.yaml --dry-run # Preview first
xfg sync -c config.yaml # Apply
Complete Example¶
id: org-standards
deleteOrphaned: true
prOptions:
merge: auto
mergeStrategy: squash
settings:
rulesets:
main-protection:
target: branch
enforcement: active
bypassActors:
- actorId: 2719952
actorType: Integration
bypassMode: always
conditions:
refName:
include:
- refs/heads/main
rules:
- type: pull_request
parameters:
requiredApprovingReviewCount: 1
dismissStaleReviewsOnPush: true
requireCodeOwnerReview: true
- type: required_status_checks
parameters:
strictRequiredStatusChecksPolicy: true
requiredStatusChecks:
- context: "ci/build"
- context: "ci/test"
- type: required_linear_history
release-protection:
target: branch
enforcement: active
conditions:
refName:
include:
- refs/heads/release/*
rules:
- type: pull_request
parameters:
requiredApprovingReviewCount: 2
- type: required_signatures
files:
.github/dependabot.yml:
content:
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
repos:
- git:
- git@github.com:your-org/frontend.git
- git@github.com:your-org/backend.git
- git@github.com:your-org/shared-lib.git