refactor graphql code into github helper class

This commit is contained in:
Peter Evans 2024-08-02 16:24:55 +00:00
parent 74416df758
commit 3a7a677a14
5 changed files with 343 additions and 1153 deletions

View file

@ -2,7 +2,7 @@ import {
createOrUpdateBranch,
tryFetch,
getWorkingBaseAndType,
buildFileChanges
buildBranchFileChanges
} from '../lib/create-or-update-branch'
import * as fs from 'fs'
import {GitCommandManager} from '../lib/git-command-manager'
@ -230,15 +230,15 @@ describe('create-or-update-branch tests', () => {
expect(workingBaseType).toEqual('commit')
})
it('tests buildFileChanges with addition and modification', async () => {
it('tests buildBranchFileChanges with addition and modification', async () => {
await git.checkout(BRANCH, BASE)
const changes = await createChanges()
await git.exec(['add', '-A'])
await git.commit(['-m', 'Test changes'])
const fileChanges = await buildFileChanges(git, BASE, BRANCH)
const branchFileChanges = await buildBranchFileChanges(git, BASE, BRANCH)
expect(fileChanges.additions).toEqual([
expect(branchFileChanges.additions).toEqual([
{
path: TRACKED_FILE,
contents: Buffer.from(changes.tracked, 'binary').toString('base64')
@ -248,10 +248,10 @@ describe('create-or-update-branch tests', () => {
contents: Buffer.from(changes.untracked, 'binary').toString('base64')
}
])
expect(fileChanges.deletions.length).toEqual(0)
expect(branchFileChanges.deletions.length).toEqual(0)
})
it('tests buildFileChanges with addition and deletion', async () => {
it('tests buildBranchFileChanges with addition and deletion', async () => {
await git.checkout(BRANCH, BASE)
const changes = await createChanges()
const TRACKED_FILE_NEW_PATH = 'c/tracked-file.txt'
@ -261,9 +261,9 @@ describe('create-or-update-branch tests', () => {
await git.exec(['add', '-A'])
await git.commit(['-m', 'Test changes'])
const fileChanges = await buildFileChanges(git, BASE, BRANCH)
const branchFileChanges = await buildBranchFileChanges(git, BASE, BRANCH)
expect(fileChanges.additions).toEqual([
expect(branchFileChanges.additions).toEqual([
{
path: UNTRACKED_FILE,
contents: Buffer.from(changes.untracked, 'binary').toString('base64')
@ -273,10 +273,10 @@ describe('create-or-update-branch tests', () => {
contents: Buffer.from(changes.tracked, 'binary').toString('base64')
}
])
expect(fileChanges.deletions).toEqual([{path: TRACKED_FILE}])
expect(branchFileChanges.deletions).toEqual([{path: TRACKED_FILE}])
})
it('tests buildFileChanges with binary files', async () => {
it('tests buildBranchFileChanges with binary files', async () => {
await git.checkout(BRANCH, BASE)
const filename = 'c/untracked-binary-file'
const filepath = path.join(REPO_PATH, filename)
@ -286,15 +286,15 @@ describe('create-or-update-branch tests', () => {
await git.exec(['add', '-A'])
await git.commit(['-m', 'Test changes'])
const fileChanges = await buildFileChanges(git, BASE, BRANCH)
const branchFileChanges = await buildBranchFileChanges(git, BASE, BRANCH)
expect(fileChanges.additions).toEqual([
expect(branchFileChanges.additions).toEqual([
{
path: filename,
contents: binaryData.toString('base64')
}
])
expect(fileChanges.deletions.length).toEqual(0)
expect(branchFileChanges.deletions.length).toEqual(0)
})
it('tests no changes resulting in no new branch being created', async () => {

1069
dist/index.js vendored

File diff suppressed because it is too large Load diff

View file

@ -48,12 +48,12 @@ export async function tryFetch(
}
}
export async function buildFileChanges(
export async function buildBranchFileChanges(
git: GitCommandManager,
base: string,
branch: string
): Promise<FileChanges> {
const fileChanges: FileChanges = {
): Promise<BranchFileChanges> {
const branchFileChanges: BranchFileChanges = {
additions: [],
deletions: []
}
@ -67,17 +67,17 @@ export async function buildFileChanges(
])
const repoPath = git.getWorkingDirectory()
for (const file of changedFiles) {
fileChanges.additions!.push({
branchFileChanges.additions!.push({
path: file,
contents: utils.readFileBase64([repoPath, file])
})
}
for (const file of deletedFiles) {
fileChanges.deletions!.push({
branchFileChanges.deletions!.push({
path: file
})
}
return fileChanges
return branchFileChanges
}
// Return the number of commits that branch2 is ahead of branch1
@ -143,7 +143,7 @@ function splitLines(multilineString: string): string[] {
.filter(x => x !== '')
}
interface FileChanges {
export interface BranchFileChanges {
additions: {
path: string
contents: string
@ -158,7 +158,7 @@ interface CreateOrUpdateBranchResult {
base: string
hasDiffWithBase: boolean
headSha: string
fileChanges?: FileChanges
branchFileChanges?: BranchFileChanges
}
export async function createOrUpdateBranch(
@ -334,7 +334,7 @@ export async function createOrUpdateBranch(
}
if (result.hasDiffWithBase) {
result.fileChanges = await buildFileChanges(git, base, branch)
result.branchFileChanges = await buildBranchFileChanges(git, base, branch)
}
// Get the pull request branch SHA

View file

@ -1,11 +1,4 @@
import * as core from '@actions/core'
import {graphql} from '@octokit/graphql'
import type {
Repository,
Ref,
Commit,
FileChanges
} from '@octokit/graphql-schema'
import {
createOrUpdateBranch,
getWorkingBaseAndType,
@ -203,203 +196,13 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
`Pushing pull request branch to '${branchRemoteName}/${inputs.branch}'`
)
if (inputs.signCommit) {
core.info(`Use API to push a signed commit`)
const graphqlWithAuth = graphql.defaults({
headers: {
authorization: 'token ' + inputs.token
}
})
const [repoOwner, repoName] = branchRepository.split('/')
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
}
await githubHelper.pushSignedCommit(
branchRepository,
inputs.branch,
inputs.base,
inputs.commitMessage,
result.branchFileChanges
)
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
}
}
}
`
const 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)
// const changedFiles = await git.getChangedFiles(
// branchRef.repository.ref!.target!.oid,
// ['--diff-filter=M']
// )
// const deletedFiles = await git.getChangedFiles(
// branchRef.repository.ref!.target!.oid,
// ['--diff-filter=D']
// )
// const fileChanges = <FileChanges>{additions: [], deletions: []}
// core.debug(`Changed files: '${JSON.stringify(changedFiles)}'`)
// core.debug(`Deleted files: '${JSON.stringify(deletedFiles)}'`)
// for (const file of changedFiles) {
// core.debug(`Reading contents of file: '${file}'`)
// fileChanges.additions!.push({
// path: file,
// contents: utils.readFileBase64([repoPath, file])
// })
// }
// for (const file of deletedFiles) {
// core.debug(`Marking file as deleted: '${file}'`)
// fileChanges.deletions!.push({
// path: file
// })
// }
const fileChanges = <FileChanges>{
additions: result.fileChanges!.additions,
deletions: result.fileChanges!.deletions
}
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
}
const pushCommitVarsWithoutContents = {
...pushCommitVars,
fileChanges: {
...pushCommitVars.fileChanges,
additions: pushCommitVars.fileChanges.additions?.map(addition => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const {contents, ...rest} = addition
return rest
})
}
}
core.debug(
`Push commit with payload: '${JSON.stringify(pushCommitVarsWithoutContents)}'`
)
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',

View file

@ -1,6 +1,13 @@
import * as core from '@actions/core'
import {Inputs} from './create-pull-request'
import {Octokit, OctokitOptions} from './octokit-client'
import type {
Repository as TempRepository,
Ref,
Commit,
FileChanges
} from '@octokit/graphql-schema'
import {BranchFileChanges} from './create-or-update-branch'
import * as utils from './utils'
const ERROR_PR_REVIEW_TOKEN_SCOPE =
@ -184,4 +191,171 @@ export class GitHubHelper {
return pull
}
async pushSignedCommit(
branchRepository: string,
branch: string,
base: string,
commitMessage: string,
branchFileChanges?: BranchFileChanges
): Promise<void> {
core.info(`Use API to push a signed commit`)
const [repoOwner, repoName] = branchRepository.split('/')
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 this.octokit.graphql<{repository: TempRepository}>(
refQuery,
{
repoOwner: repoOwner,
repoName: repoName,
branchName: branch
}
)
core.debug(
`Fetched information for branch '${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 - '${branch}'`)
branchRef = await this.octokit.graphql<{repository: TempRepository}>(
refQuery,
{
repoOwner: repoOwner,
repoName: repoName,
branchName: base
}
)
core.debug(
`Fetched information for base branch '${base}' - '${JSON.stringify(branchRef)}'`
)
core.info(
`Creating new branch '${branch}' from '${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
}
}
}
`
const newBranch = await this.octokit.graphql<{createRef: {ref: Ref}}>(
newBranchMutation,
{
repoId: branchRef.repository.id,
oid: branchRef.repository.ref.target!.oid,
branchName: 'refs/heads/' + branch
}
)
core.debug(
`Created new branch '${branch}': '${JSON.stringify(newBranch.createRef.ref)}'`
)
}
}
core.info(
`Hash ref of branch '${branch}' is '${JSON.stringify(branchRef.repository.ref!.target!.oid)}'`
)
const fileChanges = <FileChanges>{
additions: branchFileChanges!.additions,
deletions: branchFileChanges!.deletions
}
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: branch,
repoNameWithOwner: repoOwner + '/' + repoName,
headOid: branchRef.repository.ref!.target!.oid,
commitMessage: commitMessage,
fileChanges: fileChanges
}
const pushCommitVarsWithoutContents = {
...pushCommitVars,
fileChanges: {
...pushCommitVars.fileChanges,
additions: pushCommitVars.fileChanges.additions?.map(addition => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const {contents, ...rest} = addition
return rest
})
}
}
core.debug(
`Push commit with payload: '${JSON.stringify(pushCommitVarsWithoutContents)}'`
)
const commit = await this.octokit.graphql<{
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}'`
)
}
}