Skip to content

Commit

Permalink
Add support for signed commits (#3055)
Browse files Browse the repository at this point in the history
  • Loading branch information
rustycl0ck authored and peter-evans committed Aug 6, 2024
1 parent 3707121 commit e464e6e
Show file tree
Hide file tree
Showing 8 changed files with 48,785 additions and 23,577 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ All inputs are **optional**. If not set, sensible defaults will be used.
| `team-reviewers` | A comma or newline-separated list of GitHub teams to request a review from. Note that a `repo` scoped [PAT](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token), or equivalent [GitHub App permissions](docs/concepts-guidelines.md#authenticating-with-github-app-generated-tokens), are required. | |
| `milestone` | The number of the milestone to associate this pull request with. | |
| `draft` | Create a [draft pull request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests#draft-pull-requests). It is not possible to change draft status after creation except through the web interface. | `false` |
| `sign-commit` | Sign the commit as bot [refer: [Signature verification for bots](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification#signature-verification-for-bots)]. This can be useful if your repo or org has enforced commit-signing. | `false` |

#### commit-message

Expand Down
3 changes: 3 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ inputs:
draft:
description: 'Create a draft pull request. It is not possible to change draft status after creation except through the web interface'
default: false
sign-commit:
description: 'Sign the commit as github-actions bot (and as custom app if a different github-token is provided)'
default: false
outputs:
pull-request-number:
description: 'The pull request number'
Expand Down
69,041 changes: 46,994 additions & 22,047 deletions dist/index.js

Large diffs are not rendered by default.

3,112 changes: 1,589 additions & 1,523 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
"@actions/core": "^1.10.1",
"@actions/exec": "^1.1.1",
"@octokit/core": "^4.2.4",
"@octokit/graphql": "^8.1.1",
"@octokit/graphql-schema": "^15.25.0",
"@octokit/plugin-paginate-rest": "^5.0.1",
"@octokit/plugin-rest-endpoint-methods": "^6.8.1",
"proxy-from-env": "^1.1.0",
Expand All @@ -55,6 +57,6 @@
"js-yaml": "^4.1.0",
"prettier": "^3.3.3",
"ts-jest": "^29.2.4",
"typescript": "^4.9.5"
"typescript": "^5.5.4"
}
}
188 changes: 183 additions & 5 deletions src/create-pull-request.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import * as core from '@actions/core'
import * as fs from 'fs'
import { graphql } from '@octokit/graphql'
import type {
Repository,
Ref,
Commit,
FileChanges
} from '@octokit/graphql-schema'
import {
createOrUpdateBranch,
getWorkingBaseAndType,
Expand Down Expand Up @@ -32,6 +40,7 @@ export interface Inputs {
teamReviewers: string[]
milestone: number
draft: boolean
signCommit: boolean
}

export async function createPullRequest(inputs: Inputs): Promise<void> {
Expand Down Expand Up @@ -192,11 +201,180 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
core.startGroup(
`Pushing pull request branch to '${branchRemoteName}/${inputs.branch}'`
)
await git.push([
'--force-with-lease',
branchRemoteName,
`${inputs.branch}:refs/heads/${inputs.branch}`
])
if (inputs.signCommit) {
core.info(`Use API to push a signed commit`)
const graphqlWithAuth = graphql.defaults({
headers: {
authorization: 'token ' + inputs.token,
},
});

let repoOwner = process.env.GITHUB_REPOSITORY!.split("/")[0]
if (inputs.pushToFork) {
const forkName = await githubHelper.getRepositoryParent(baseRemote.repository)
if (!forkName) { repoOwner = forkName! }
}
const repoName = process.env.GITHUB_REPOSITORY!.split("/")[1]

core.debug(`repoOwner: '${repoOwner}', repoName: '${repoName}'`)
const refQuery = `
query GetRefId($repoName: String!, $repoOwner: String!, $branchName: String!) {
repository(owner: $repoOwner, name: $repoName){
id
ref(qualifiedName: $branchName){
id
name
prefix
target{
id
oid
commitUrl
commitResourcePath
abbreviatedOid
}
}
},
}
`

let branchRef = await graphqlWithAuth<{repository: Repository}>(
refQuery,
{
repoOwner: repoOwner,
repoName: repoName,
branchName: inputs.branch
}
)
core.debug( `Fetched information for branch '${inputs.branch}' - '${JSON.stringify(branchRef)}'`)

// if the branch does not exist, then first we need to create the branch from base
if (branchRef.repository.ref == null) {
core.debug( `Branch does not exist - '${inputs.branch}'`)
branchRef = await graphqlWithAuth<{repository: Repository}>(
refQuery,
{
repoOwner: repoOwner,
repoName: repoName,
branchName: inputs.base
}
)
core.debug( `Fetched information for base branch '${inputs.base}' - '${JSON.stringify(branchRef)}'`)

core.info( `Creating new branch '${inputs.branch}' from '${inputs.base}', with ref '${JSON.stringify(branchRef.repository.ref!.target!.oid)}'`)
if (branchRef.repository.ref != null) {
core.debug( `Send request for creating new branch`)
const newBranchMutation = `
mutation CreateNewBranch($branchName: String!, $oid: GitObjectID!, $repoId: ID!) {
createRef(input: {
name: $branchName,
oid: $oid,
repositoryId: $repoId
}) {
ref {
id
name
prefix
}
}
}
`
let newBranch = await graphqlWithAuth<{createRef: {ref: Ref}}>(
newBranchMutation,
{
repoId: branchRef.repository.id,
oid: branchRef.repository.ref.target!.oid,
branchName: 'refs/heads/' + inputs.branch
}
)
core.debug(`Created new branch '${inputs.branch}': '${JSON.stringify(newBranch.createRef.ref)}'`)
}
}
core.info( `Hash ref of branch '${inputs.branch}' is '${JSON.stringify(branchRef.repository.ref!.target!.oid)}'`)

// switch to input-branch for reading updated file contents
await git.checkout(inputs.branch)

let changedFiles = await git.getChangedFiles(branchRef.repository.ref!.target!.oid, ['--diff-filter=M'])
let deletedFiles = await git.getChangedFiles(branchRef.repository.ref!.target!.oid, ['--diff-filter=D'])
let fileChanges = <FileChanges>{additions: [], deletions: []}

core.debug(`Changed files: '${JSON.stringify(changedFiles)}'`)
core.debug(`Deleted files: '${JSON.stringify(deletedFiles)}'`)

for (var file of changedFiles) {
fileChanges.additions!.push({
path: file,
contents: btoa(fs.readFileSync(file, 'utf8')),
})
}

for (var file of deletedFiles) {
fileChanges.deletions!.push({
path: file,
})
}

const pushCommitMutation = `
mutation PushCommit(
$repoNameWithOwner: String!,
$branchName: String!,
$headOid: GitObjectID!,
$commitMessage: String!,
$fileChanges: FileChanges
) {
createCommitOnBranch(input: {
branch: {
repositoryNameWithOwner: $repoNameWithOwner,
branchName: $branchName,
}
fileChanges: $fileChanges
message: {
headline: $commitMessage
}
expectedHeadOid: $headOid
}){
clientMutationId
ref{
id
name
prefix
}
commit{
id
abbreviatedOid
oid
}
}
}
`
const pushCommitVars = {
branchName: inputs.branch,
repoNameWithOwner: repoOwner + '/' + repoName,
headOid: branchRef.repository.ref!.target!.oid,
commitMessage: inputs.commitMessage,
fileChanges: fileChanges,
}

core.info(`Push commit with payload: '${JSON.stringify(pushCommitVars)}'`)

const commit = await graphqlWithAuth<{createCommitOnBranch: {ref: Ref, commit: Commit} }>(
pushCommitMutation,
pushCommitVars,
);

core.debug( `Pushed commit - '${JSON.stringify(commit)}'`)
core.info( `Pushed commit with hash - '${commit.createCommitOnBranch.commit.oid}' on branch - '${commit.createCommitOnBranch.ref.name}'`)

// switch back to previous branch/state since we are done with reading the changed file contents
await git.checkout('-')

} else {
await git.push([
'--force-with-lease',
branchRemoteName,
`${inputs.branch}:refs/heads/${inputs.branch}`
])
}
core.endGroup()
}

Expand Down
10 changes: 10 additions & 0 deletions src/git-command-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,16 @@ export class GitCommandManager {
return output.exitCode === 1
}

async getChangedFiles(ref: string, options?: string[]): Promise<string[]> {
const args = ['diff', '--name-only']
if (options) {
args.push(...options)
}
args.push(ref)
const output = await this.exec(args)
return output.stdout.split("\n").filter((filename) => filename != '')
}

async isDirty(untracked: boolean, pathspec?: string[]): Promise<boolean> {
const pathspecArgs = pathspec ? ['--', ...pathspec] : []
// Check untracked changes
Expand Down
3 changes: 2 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ async function run(): Promise<void> {
reviewers: utils.getInputAsArray('reviewers'),
teamReviewers: utils.getInputAsArray('team-reviewers'),
milestone: Number(core.getInput('milestone')),
draft: core.getBooleanInput('draft')
draft: core.getBooleanInput('draft'),
signCommit: core.getBooleanInput('sign-commit'),
}
core.debug(`Inputs: ${inspect(inputs)}`)

Expand Down

0 comments on commit e464e6e

Please sign in to comment.