Problem
The GitHub emulator has no GraphQL endpoint, so clients that commit files with the createCommitOnBranch mutation cannot run against it. Verified on main at b75beb7 with npx emulate@0.13.0.
Clients use this mutation instead of the Contents or Git Data REST APIs because GitHub signs createCommitOnBranch commits for any credential, including personal access tokens. Repositories with a ruleset that requires signed commits accept these commits, whereas GitHub signs Git Data REST commits only when created with a GitHub App installation token.
Reproduction
Seed octocat/hello-world with auto_init: true and a repro_token for octocat, then start the GitHub service:
npx emulate@0.13.0 start --service github --port 4001 --seed emulate.config.yamlRead the branch head, then send the mutation:
curl -s http://localhost:4001/repos/octocat/hello-world/git/ref/heads/main \
-H 'Authorization: Bearer repro_token' | jq -r .object.sha
# b0e7d0c3b2018d938a6bf95e70bea2425454c335
curl -s -i -X POST http://localhost:4001/graphql \
-H 'Authorization: Bearer repro_token' -H 'Content-Type: application/json' \
-d '{"query":"mutation($input: CreateCommitOnBranchInput!) { createCommitOnBranch(input: $input) { commit { oid } } }","variables":{"input":{"branch":{"repositoryNameWithOwner":"octocat/hello-world","branchName":"main"},"expectedHeadOid":"b0e7d0c3b2018d938a6bf95e70bea2425454c335","message":{"headline":"Add file"},"fileChanges":{"additions":[{"path":"a.txt","contents":"aGVsbG8K"}]}}}}'Result:
HTTP/1.1 404 Not Found
content-type: application/json; charset=UTF-8
{"message":"Not Found","documentation_url":"https://emulate.dev/github"}GitHub behavior
POST /graphql with this input returns HTTP 200 and {"data":{"createCommitOnBranch":{"commit":{"oid":"<new sha>"}}}}. On success, GitHub:
- creates one commit whose parent is
expectedHeadOid, applyingfileChanges.additions(base64contents) andfileChanges.deletions(pathonly); - moves
refs/heads/<branchName>to the new commit; - sets the author to the authenticated account, sets the committer to GitHub, and marks the commit as verified.
When the commit fails, GitHub returns HTTP 200 with an errors[] array where each entry carries a type:
| Case | type | Message |
|---|---|---|
expectedHeadOid is not the branch head | STALE_DATA | contains is at <actual oid> but expected <expected oid> |
| Repository or branch not found | NOT_FOUND | |
| Token cannot write to the repository | FORBIDDEN | |
| Classic branch protection rejects the update | BRANCH_PROTECTION_RULE_VIOLATION |
Clients detect a stale head and retry based on type, or by matching the is at ... but expected ... text, which GitHub has also returned with type FORBIDDEN.
Docs: https://docs.github.com/en/graphql/reference/mutations#createcommitonbranch and https://docs.github.com/en/graphql/reference/input-objects#createcommitonbranchinput
Proposed scope
- Support
createCommitOnBranchwithbranch.repositoryNameWithOwnerorbranch.repositoryId,expectedHeadOid,message.headline, optionalmessage.body, andfileChanges.additionsandfileChanges.deletions. - Return
commit { oid url }. OtherCommitfields can follow later. - Return GraphQL
errors[]with HTTP200using thetypevalues above:STALE_DATA(with theis at ... but expected ...message),NOT_FOUND, andFORBIDDEN.BRANCH_PROTECTION_RULE_VIOLATIONapplies once branch protection blocks the branch. Write access follows the REST permission rules that installation tokens already use. - Send a
pushwebhook for the branch update, as Contents API writes already do. - Setting
verification.verified: trueon the resulting commit in REST responses is optional. Clients primarily need the commit and the ref update.
Proposed implementation
The commit machinery already exists. The Contents API commitFiles helper accepts a map of path changes (a new entry, or null to delete) and builds one tree and commit on top of the branch head. The PUT handler then moves refs/heads/<branch> and sends the push webhook:
The mutation can map fileChanges onto that changes map and reuse the same branch update and permission check, adding a head check against expectedHeadOid like the expected_head_sha check in update-branch:
Three open pull requests add a GitHub /graphql route: #213 (createIssue), #265 (review threads), and #337 (pull requests, viewer). This mutation can build on whichever pull request merges first, rather than introducing a fourth GraphQL route.
Activity
0No activity yet.