Skip to content

Commit

Permalink
feat: ✨ Use GraphQL API for signed empty commits (#787)
Browse files Browse the repository at this point in the history
  • Loading branch information
robvanderleek authored Dec 2, 2023
1 parent aa0070f commit 6a1638a
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 47 deletions.
54 changes: 24 additions & 30 deletions src/github.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,33 +225,6 @@ async function getBranchHeadSha (ctx, branch) {
}
}

async function getCommitTreeSha (ctx, commitSha) {
const owner = context.getRepoOwnerLogin(ctx)
const repo = context.getRepoName(ctx)
const res = await ctx.octokit.git.getCommit({ owner, repo, commit_sha: commitSha })
return res.data.tree.sha
}

async function createCommit (ctx, commitSha, treeSha, username, message) {
const owner = context.getRepoOwnerLogin(ctx)
const repo = context.getRepoName(ctx)
const res = await ctx.octokit.git.createCommit({
owner,
repo,
message,
tree: treeSha,
parents: [commitSha],
author: { name: username, email: `${username}@users.noreply.github.com` }
})
return res.data.sha
}

async function updateReference (ctx, branchName, sha) {
const owner = context.getRepoOwnerLogin(ctx)
const repo = context.getRepoName(ctx)
await ctx.octokit.git.updateRef({ owner, repo, ref: `heads/${branchName}`, sha })
}

async function createBranch (ctx, config, branchName, sha, log) {
const owner = context.getRepoOwnerLogin(ctx)
const repo = context.getRepoName(ctx)
Expand Down Expand Up @@ -289,9 +262,7 @@ async function createPr (app, ctx, config, username, branchName) {
const branchHeadSha = await getBranchHeadSha(ctx, branchName)
if (branchHeadSha === baseHeadSha) {
app.log('Branch and base heads are equal, creating empty commit for PR')
const treeSha = await getCommitTreeSha(ctx, branchHeadSha)
const emptyCommitSha = await createCommit(ctx, branchHeadSha, treeSha, username, getCommitText(ctx, config))
await updateReference(ctx, branchName, emptyCommitSha)
await createEmptyCommit(ctx, branchName, getCommitText(ctx, config), branchHeadSha)
}
const { data: pr } = await ctx.octokit.pulls.create(
{ owner, repo, head: branchName, base, title, body: getPrBody(app, ctx, config), draft: draft })
Expand All @@ -303,6 +274,29 @@ async function createPr (app, ctx, config, username, branchName) {
}
}

async function createEmptyCommit (ctx, branchName, message, headSha) {
const owner = context.getRepoOwnerLogin(ctx)
const repo = context.getRepoName(ctx)
const createEptyCommitMutation = `
mutation($repositoryNameWithOwner: String!, $branchName: String!, $message: String!, $headSha: GitObjectID!) {
createCommitOnBranch(
input: {
branch: {repositoryNameWithOwner: $repositoryNameWithOwner, branchName: $branchName},
message: {headline: $message},
fileChanges: {},
expectedHeadOid: $headSha
}
) {
commit {
url
}
}
}`
await ctx.octokit.graphql(createEptyCommitMutation, {
repositoryNameWithOwner: `${owner}/${repo}`, branchName: branchName, message: message, headSha: headSha
})
}

function getCommitText (ctx, config) {
const draft = Config.shouldOpenDraftPR(config)
const draftText = draft ? 'draft ' : ''
Expand Down
36 changes: 19 additions & 17 deletions tests/github.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,11 @@ test('Retry create comment when it fails', async () => {
test('create (draft) PR', async () => {
const createPR = jest.fn()
let capturedCommitMessage = ''
const createCommit = ({ message }) => {
capturedCommitMessage = message
return ({ data: { sha: 'abcd1234' } })
}
const ctx = helpers.getDefaultContext()
ctx.octokit.pulls.create = createPR
ctx.octokit.git.createCommit = createCommit
ctx.octokit.graphql = (_, { message }) => {
capturedCommitMessage = message
}

await github.createPr({ log: () => { } }, ctx, { silent: false }, 'robvanderleek', 'issue-1')
expect(createPR).toHaveBeenCalledWith({
Expand Down Expand Up @@ -237,11 +235,10 @@ test('create (draft) PR', async () => {

test('copy Issue description into PR', async () => {
const createPR = jest.fn()
const createCommit = () => ({ data: { sha: 'abcd1234' } })
const ctx = helpers.getDefaultContext()
ctx.octokit.pulls.create = createPR
ctx.octokit.git.createCommit = createCommit
ctx.payload.issue.body = 'This is the description'
ctx.octokit.graphql = jest.fn()

await github.createPr({ log: () => { } }, ctx, { copyIssueDescriptionToPR: true, silent: false }, 'robvanderleek',
'issue-1')
Expand All @@ -259,11 +256,10 @@ test('copy Issue description into PR', async () => {

test('Do not copy undefined Issue description into PR', async () => {
const createPR = jest.fn()
const createCommit = () => ({ data: { sha: 'abcd1234' } })
const ctx = helpers.getDefaultContext()
ctx.octokit.pulls.create = createPR
ctx.octokit.git.createCommit = createCommit
ctx.payload.issue.body = null
ctx.octokit.graphql = jest.fn()

await github.createPr({ log: () => { } }, ctx, { copyIssueDescriptionToPR: true, silent: false }, 'robvanderleek',
'issue-1')
Expand All @@ -282,6 +278,7 @@ test('use correct source branch', async () => {
const createPR = jest.fn()
const ctx = helpers.getDefaultContext()
ctx.octokit.pulls.create = createPR
ctx.octokit.graphql = jest.fn()
ctx.payload.issue.labels = [{ name: 'enhancement' }]
const config = { branches: [{ label: 'enhancement', name: 'develop' }] }

Expand All @@ -301,6 +298,7 @@ test('use configured target branch', async () => {
const createPR = jest.fn()
const ctx = helpers.getDefaultContext()
ctx.octokit.pulls.create = createPR
ctx.octokit.graphql = jest.fn()
ctx.payload.issue.labels = [{ name: 'enhancement' }]
const config = { branches: [{ label: 'enhancement', prTarget: 'develop' }] }

Expand All @@ -320,6 +318,7 @@ test('configured source and target branch', async () => {
const createPR = jest.fn()
const ctx = helpers.getDefaultContext()
ctx.octokit.pulls.create = createPR
ctx.octokit.graphql = jest.fn()
ctx.payload.issue.labels = [{ name: 'hotfix' }]
const config = { branches: [{ label: 'hotfix', name: 'develop', prTarget: 'hotfix' }] }

Expand All @@ -337,11 +336,10 @@ test('configured source and target branch', async () => {

test('copy Issue milestone into PR', async () => {
const updateIssue = jest.fn()
const createCommit = () => ({ data: { sha: 'abcd1234' } })
const ctx = helpers.getDefaultContext()
ctx.octokit.pulls.create = () => ({ data: { number: 123 } })
ctx.octokit.issues.update = updateIssue
ctx.octokit.git.createCommit = createCommit
ctx.octokit.graphql = jest.fn()
ctx.payload.issue.body = 'This is the description'
ctx.payload.issue.milestone = { number: 456 }

Expand All @@ -353,27 +351,31 @@ test('copy Issue milestone into PR', async () => {
})

test('empty commit text', async () => {
const createCommit = jest.fn()
const ctx = helpers.getDefaultContext()
ctx.octokit.pulls.create = () => ({ data: { number: 123 } })
ctx.octokit.git.createCommit = createCommit
let capturedCommitMessage = ''
ctx.octokit.graphql = (_, { message }) => {
capturedCommitMessage = message
}
ctx.payload.issue.body = 'This is the description'
ctx.payload.issue.milestone = { number: 456 }

await github.createPr({ log: () => { } }, ctx, {}, 'robvanderleek', 'issue-1')

expect(createCommit.mock.calls[0][0].message).toBe('Create PR for #1')
expect(capturedCommitMessage).toBe('Create PR for #1')
})

test('empty commit with skip CI text', async () => {
const createCommit = jest.fn()
const ctx = helpers.getDefaultContext()
ctx.octokit.pulls.create = () => ({ data: { number: 123 } })
ctx.octokit.git.createCommit = createCommit
let capturedCommitMessage = ''
ctx.octokit.graphql = (_, { message }) => {
capturedCommitMessage = message
}
ctx.payload.issue.body = 'This is the description'
ctx.payload.issue.milestone = { number: 456 }

await github.createPr({ log: () => { } }, ctx, { prSkipCI: true }, 'robvanderleek', 'issue-1')

expect(createCommit.mock.calls[0][0].message).toBe('Create PR for #1\n[skip ci]')
expect(capturedCommitMessage).toBe('Create PR for #1\n[skip ci]')
})

1 comment on commit 6a1638a

@vercel
Copy link

@vercel vercel bot commented on 6a1638a Dec 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.