#348

GitHub: GraphQL `createCommitOnBranch` mutation (feature request)

Openwaynesun09opened 6d ago

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.yaml

Read 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, applying fileChanges.additions (base64 contents) and fileChanges.deletions (path only);
  • 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:

CasetypeMessage
expectedHeadOid is not the branch headSTALE_DATAcontains is at <actual oid> but expected <expected oid>
Repository or branch not foundNOT_FOUND
Token cannot write to the repositoryFORBIDDEN
Classic branch protection rejects the updateBRANCH_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 createCommitOnBranch with branch.repositoryNameWithOwner or branch.repositoryId, expectedHeadOid, message.headline, optional message.body, and fileChanges.additions and fileChanges.deletions.
  • Return commit { oid url }. Other Commit fields can follow later.
  • Return GraphQL errors[] with HTTP 200 using the type values above: STALE_DATA (with the is at ... but expected ... message), NOT_FOUND, and FORBIDDEN. BRANCH_PROTECTION_RULE_VIOLATION applies once branch protection blocks the branch. Write access follows the REST permission rules that installation tokens already use.
  • Send a push webhook for the branch update, as Contents API writes already do.
  • Setting verification.verified: true on 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:

https://github.com/vercel-labs/emulate/blob/b75beb7ce7d78676d471fff47432b77be6ff6528/packages/@emulators/github/src/routes/contents.ts#L417-L470

https://github.com/vercel-labs/emulate/blob/b75beb7ce7d78676d471fff47432b77be6ff6528/packages/@emulators/github/src/routes/contents.ts#L610

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:

https://github.com/vercel-labs/emulate/blob/b75beb7ce7d78676d471fff47432b77be6ff6528/packages/@emulators/github/src/routes/pulls.ts#L917-L918

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

0

No activity yet.