Reputation: 606
I know you can update an existing branch protection rule via the API, but I cannot find any references in the v3 API docs WRT creating a new rule. For example, if I want to add a rule to a repo that matches a new branch prefixed with "dev_", I have to add it through the GUI, using the "Apply rule to" field, then I can use the API to update those rule settings. Ideally, I'd like to have a hook that does this automatically if a new branch is introduced to the repo, but does not match an existing rule. I should be able to create that rule through the API. Is there a way to do this?
Upvotes: 13
Views: 11654
Reputation: 1235
FYI for those finding this now - there is an API so you don't have to use GraphQL if you don't want.
curl \
-X PUT \
-H "Accept: application/vnd.github+json" \
-H "Authorization: token <TOKEN>" \
https://api.github.com/repos/OWNER/REPO/branches/BRANCH/protection \
-d '{"required_status_checks":{"strict":true,"contexts":["continuous-integration/travis-ci"]},"enforce_admins":true,"required_pull_request_reviews":{"dismissal_restrictions":{"users":["octocat"],"teams":["justice-league"]},"dismiss_stale_reviews":true,"require_code_owner_reviews":true,"required_approving_review_count":2,"bypass_pull_request_allowances":{"users":["octocat"],"teams":["justice-league"]}},"restrictions":{"users":["octocat"],"teams":["justice-league"],"apps":["super-ci"]},"required_linear_history":true,"allow_force_pushes":true,"allow_deletions":true,"block_creations":true,"required_conversation_resolution":true}'
Edit: Even though the API says this is for update
, it does create new branch protection rules as well.
Upvotes: 7
Reputation: 5258
It appears that GitHub has decided to add new features via the GraphQL API only, not via the REST API. So while this is impossible with the REST API, you can do it using the GraphQL createBranchProtectionRule
mutation.
In case it helps anyone, I wrote a script to do this, using GitHub's gh
CLI:
#!/bin/bash
set -ue
err() { echo 1>&2 "$*"; }
die() { err "ERROR: $*"; exit 1; }
mustBool() {
[[ "${1#*=}" = "true" || "${1#*=}" = "false" ]] ||
die "bad boolean property value: $1"
}
mustInt() {
[[ "${1#*=}" =~ [0-9]+ ]] ||
die "bad integer property value: $1"
}
[ $# -ge 4 ] || {
err "usage: $0 HOSTNAME ORG REPO PATTERN [PROPERTIES...]"
err " where PROPERTIES can be:"
err " dismissesStaleReviews=true|false"
err " requiresApprovingReviewCount=INTEGER"
err " requiresApprovingReviews=true|false"
err " requiresCodeOwnerReviews=true|false"
err " restrictPushes=true|false"
exit 1
}
hostname="$1"
org="$2"
repo="$3"
pattern="$4"
shift 4
repoNodeId="$(gh api --hostname "$hostname" "repos/$org/$repo" --jq .node_id)"
[[ -n "$repoNodeId" ]] || die "could not determine repo nodeId"
graphql="
mutation createBranchProtectionRule {
createBranchProtectionRule(input: {
repositoryId: \"$repoNodeId\"
pattern: \"$pattern\""
seen=()
requiredStatusCheckContexts=()
for property in "$@"; do
for eSeen in "${seen[@]:-}"; do
[[ "${eSeen%%=*}" = "${property%%=*}" ]] &&
# Allow duplication of multivalued properties
[[ "${eSeen%%=*}" != "requiredStatusCheckContexts" ]] &&
die "Duplicate property: $property"
done
seen+=("${property}")
case "$property" in
requiredStatusCheckContexts=*)
requiredStatusCheckContexts+=("${property#*=}")
;;
\
allowsDeletions=* | \
allowsForcePushes=* | \
dismissesStaleReviews=* | \
isAdminEnforced=* | \
requiresApprovingReviews=* | \
requiresCodeOwnerReviews=* | \
requiresCommitSignatures=* | \
requiresLinearHistory=* | \
requiresStatusChecks=* | \
requiresStrictStatusChecks=* | \
restrictPushes=* | \
restrictsPushes=* | \
restrictsReviewDismissals=* \
)
mustBool "$property"
graphql="$graphql
${property%%=*}: ${property#*=}"
;;
requiredApprovingReviewCount=*)
mustInt "$property"
graphql="$graphql
${property%%=*}: ${property#*=}"
;;
*)
die "unknown property: $property"
esac
done
if [ -n "${requiredStatusCheckContexts[*]:-}" ]; then
graphql="$graphql
requiredStatusCheckContexts: [
"
i=0
for context in "${requiredStatusCheckContexts[@]}"; do
[ $i -ne 0 ] && graphql="$graphql,
"
i=$((1+$i))
graphql="$graphql"$'\t\t\t'"\"$context\""
done
graphql="$graphql
]
"
fi
graphql="$graphql
}) {
branchProtectionRule {
allowsDeletions
allowsForcePushes
creator { login }
databaseId
dismissesStaleReviews
isAdminEnforced
pattern
repository { nameWithOwner }
requiredApprovingReviewCount
requiresApprovingReviews
requiredStatusCheckContexts
requiresCodeOwnerReviews
requiresCommitSignatures
requiresLinearHistory
requiresStatusChecks
requiresStrictStatusChecks
restrictsPushes
restrictsReviewDismissals
}
clientMutationId
}
}"
gh api --hostname "$hostname" graphql -F "query=$graphql" ||
die "GraphQL update failed: $graphql"
echo ""
echo "SUCCESS: Branch protection rule successfully created"
Here is an example of invoking it it:
./createBranchProtectionRule.sh github.example.com skissane my-repo v* requiresApprovingReviews=true requiresCodeOwnerReviews=true requiredApprovingReviewCount=1 requiresStatusChecks=true requiresStrictStatusChecks=false requiredStatusCheckContexts=continuous-integration/jenkins/pr-merge requiresLinearHistory=true
which produces the following output:
{
"data": {
"createBranchProtectionRule": {
"branchProtectionRule": {
"allowsDeletions": false,
"allowsForcePushes": false,
"creator": {
"login": "skissane"
},
"databaseId": 1729,
"dismissesStaleReviews": false,
"isAdminEnforced": false,
"pattern": "v*",
"repository": {
"nameWithOwner": "skissane/my-repo"
}
"requiredApprovingReviewCount": 1,
"requiresApprovingReviews": true,
"requiredStatusCheckContexts": [
"continuous-integration/jenkins/pr-merge"
],
"requiresCodeOwnerReviews": true,
"requiresCommitSignatures": false,
"requiresLinearHistory": true,
"requiresStatusChecks": true,
"requiresStrictStatusChecks": false,
"restrictsPushes": false,
"restrictsReviewDismissals": false
},
"clientMutationId": null
}
}
}
SUCCESS: Branch protection rule successfully created
Upvotes: 6
Reputation: 1897
This did it for me for GitHub Enterprise.
curl --location --request PUT 'https://github.company.com/api/v3/repos/ORG/REPO-NAME/branches/BRANCH-NAME/protection' \
--header 'Accept: application/vnd.github.v3+json' \
--header 'Accept: application/vnd.github.luke-cage-preview+json' \
--header 'Authorization: Basic .................... \
--header 'Content-Type: text/plain' \
--data-raw '{
"required_status_checks": {
"strict": false,
"contexts": ["continuous-integration/jenkins/BRANCH-NAME"]
},
"enforce_admins": true,
"required_pull_request_reviews": {
"dismissal_restrictions": {},
"dismiss_stale_reviews": true,
"require_code_owner_reviews": true,
"required_approving_review_count": 1
},
"restrictions": {
"users": ["users"],
"teams": ["teams"],
"apps": ["apps"]
}
}
'
Upvotes: 2