create-pull-request/src/create-pull-request.ts

221 lines
7.1 KiB
TypeScript
Raw Normal View History

2020-07-16 10:57:13 +02:00
import * as core from '@actions/core'
import {createOrUpdateBranch} from './create-or-update-branch'
import {GitHubHelper} from './github-helper'
import {GitCommandManager} from './git-command-manager'
2020-07-17 13:54:39 +02:00
import {GitAuthHelper} from './git-auth-helper'
2020-07-16 10:57:13 +02:00
import * as utils from './utils'
export interface Inputs {
token: string
path: string
commitMessage: string
committer: string
author: string
signoff: boolean
2020-07-19 08:09:44 +02:00
branch: string
2020-07-20 12:14:42 +02:00
branchSuffix: string
2020-07-19 08:09:44 +02:00
base: string
pushToFork: string
2020-07-16 10:57:13 +02:00
title: string
body: string
labels: string[]
assignees: string[]
reviewers: string[]
teamReviewers: string[]
milestone: number
draft: boolean
}
export async function createPullRequest(inputs: Inputs): Promise<void> {
2020-07-17 13:54:39 +02:00
let gitAuthHelper
2020-07-16 10:57:13 +02:00
try {
// Get the repository path
const repoPath = utils.getRepoPath(inputs.path)
// Create a git command manager
const git = await GitCommandManager.create(repoPath)
2020-07-16 12:13:28 +02:00
2020-07-17 13:54:39 +02:00
// Save and unset the extraheader auth config if it exists
2020-07-16 12:13:28 +02:00
core.startGroup('Save persisted git credentials')
2020-07-17 13:54:39 +02:00
gitAuthHelper = new GitAuthHelper(git)
await gitAuthHelper.savePersistedAuth()
2020-07-16 12:13:28 +02:00
core.endGroup()
2020-07-16 10:57:13 +02:00
// Init the GitHub client
const githubHelper = new GitHubHelper(inputs.token)
core.startGroup('Determining the base and head repositories')
// Determine the base repository from git config
2020-07-17 13:54:39 +02:00
const remoteUrl = await git.tryGetRemoteUrl()
const baseRemote = utils.getRemoteDetail(remoteUrl)
// Determine the head repository; the target for the pull request branch
const branchRemoteName = inputs.pushToFork ? 'fork' : 'origin'
const branchRepository = inputs.pushToFork
? inputs.pushToFork
: baseRemote.repository
if (inputs.pushToFork) {
// Check if the supplied fork is really a fork of the base
const parentRepository = await githubHelper.getRepositoryParent(
branchRepository
)
if (parentRepository != baseRemote.repository) {
throw new Error(
`Repository '${branchRepository}' is not a fork of '${baseRemote.repository}'. Unable to continue.`
)
}
// Add a remote for the fork
const remoteUrl = utils.getRemoteUrl(
baseRemote.protocol,
branchRepository
)
await git.exec(['remote', 'add', 'fork', remoteUrl])
}
2020-07-16 12:13:28 +02:00
core.endGroup()
2020-07-16 10:57:13 +02:00
core.info(
`Pull request branch target repository set to ${branchRepository}`
2020-07-16 10:57:13 +02:00
)
// Configure auth
if (baseRemote.protocol == 'HTTPS') {
2020-07-16 12:13:28 +02:00
core.startGroup('Configuring credential for HTTPS authentication')
2020-07-17 14:06:16 +02:00
await gitAuthHelper.configureToken(inputs.token)
2020-07-16 12:13:28 +02:00
core.endGroup()
2020-07-16 10:57:13 +02:00
}
// Determine if the checked out ref is a valid base for a pull request
// The action needs the checked out HEAD ref to be a branch
// This check will fail in the following cases:
// - HEAD is detached
// - HEAD is a merge commit (pull_request events)
// - HEAD is a tag
2020-07-16 12:13:28 +02:00
core.startGroup('Checking the checked out ref')
2020-07-16 10:57:13 +02:00
const symbolicRefResult = await git.exec(
['symbolic-ref', 'HEAD', '--short'],
true
)
if (symbolicRefResult.exitCode != 0) {
core.debug(`${symbolicRefResult.stderr}`)
throw new Error(
'The checked out ref is not a valid base for a pull request. Unable to continue.'
)
}
const workingBase = symbolicRefResult.stdout.trim()
// Exit if the working base is a PR branch created by this action.
// This may occur when using a PAT instead of GITHUB_TOKEN because
// a PAT allows workflow actions to trigger further events.
if (workingBase.startsWith(inputs.branch)) {
throw new Error(
`Working base branch '${workingBase}' was created by this action. Unable to continue.`
)
}
2020-07-16 12:13:28 +02:00
core.endGroup()
2020-07-16 10:57:13 +02:00
2020-07-20 12:14:42 +02:00
// Apply the branch suffix if set
if (inputs.branchSuffix) {
switch (inputs.branchSuffix) {
case 'short-commit-hash':
// Suffix with the short SHA1 hash
inputs.branch = `${inputs.branch}-${await git.revParse('HEAD', [
'--short'
])}`
break
case 'timestamp':
// Suffix with the current timestamp
inputs.branch = `${inputs.branch}-${utils.secondsSinceEpoch()}`
break
case 'random':
// Suffix with a 7 character random string
inputs.branch = `${inputs.branch}-${utils.randomString()}`
break
default:
throw new Error(
`Branch suffix '${inputs.branchSuffix}' is not a valid value. Unable to continue.`
)
}
}
2020-07-16 10:57:13 +02:00
// Output head branch
core.info(
`Pull request branch to create or update set to '${inputs.branch}'`
)
// Configure the committer and author
2020-07-16 12:13:28 +02:00
core.startGroup('Configuring the committer and author')
const parsedAuthor = utils.parseDisplayNameEmail(inputs.author)
const parsedCommitter = utils.parseDisplayNameEmail(inputs.committer)
2020-07-16 10:57:13 +02:00
git.setIdentityGitOptions([
'-c',
`author.name=${parsedAuthor.name}`,
2020-07-16 10:57:13 +02:00
'-c',
`author.email=${parsedAuthor.email}`,
2020-07-16 10:57:13 +02:00
'-c',
`committer.name=${parsedCommitter.name}`,
2020-07-16 10:57:13 +02:00
'-c',
`committer.email=${parsedCommitter.email}`
2020-07-16 10:57:13 +02:00
])
core.info(
`Configured git committer as '${parsedCommitter.name} <${parsedCommitter.email}>'`
2020-07-16 10:57:13 +02:00
)
core.info(
`Configured git author as '${parsedAuthor.name} <${parsedAuthor.email}>'`
2020-07-16 10:57:13 +02:00
)
2020-07-16 12:13:28 +02:00
core.endGroup()
2020-07-16 10:57:13 +02:00
// Create or update the pull request branch
2020-07-16 12:13:28 +02:00
core.startGroup('Create or update the pull request branch')
2020-07-16 10:57:13 +02:00
const result = await createOrUpdateBranch(
git,
inputs.commitMessage,
inputs.base,
inputs.branch,
branchRemoteName,
inputs.signoff
2020-07-16 10:57:13 +02:00
)
2020-07-16 12:13:28 +02:00
core.endGroup()
2020-07-16 10:57:13 +02:00
if (['created', 'updated'].includes(result.action)) {
// The branch was created or updated
2020-07-16 12:13:28 +02:00
core.startGroup(
`Pushing pull request branch to '${branchRemoteName}/${inputs.branch}'`
2020-07-16 12:13:28 +02:00
)
await git.push([
'--force-with-lease',
branchRemoteName,
`HEAD:refs/heads/${inputs.branch}`
])
2020-07-16 12:13:28 +02:00
core.endGroup()
2020-07-16 10:57:13 +02:00
// Set the base. It would have been '' if not specified as an input
inputs.base = result.base
if (result.hasDiffWithBase) {
// Create or update the pull request
await githubHelper.createOrUpdatePullRequest(
inputs,
baseRemote.repository,
branchRepository
)
2020-07-16 10:57:13 +02:00
} else {
// If there is no longer a diff with the base delete the branch
core.info(
`Branch '${inputs.branch}' no longer differs from base branch '${inputs.base}'`
)
core.info(`Closing pull request and deleting branch '${inputs.branch}'`)
await git.push([
'--delete',
'--force',
branchRemoteName,
2020-07-16 10:57:13 +02:00
`refs/heads/${inputs.branch}`
])
}
}
} catch (error) {
core.setFailed(error.message)
} finally {
2020-07-17 13:54:39 +02:00
// Remove auth and restore persisted auth config if it existed
2020-07-16 12:13:28 +02:00
core.startGroup('Restore persisted git credentials')
2020-07-17 13:54:39 +02:00
await gitAuthHelper.removeAuth()
await gitAuthHelper.restorePersistedAuth()
2020-07-16 12:13:28 +02:00
core.endGroup()
2020-07-16 10:57:13 +02:00
}
}