feat: infer github server and api urls

This commit is contained in:
Peter Evans
2023-12-19 09:39:50 +00:00
parent 02a8f71e34
commit 1e6bff9d94
9 changed files with 431 additions and 401 deletions

View File

@@ -6,7 +6,7 @@ import {
} from './create-or-update-branch'
import {GitHubHelper} from './github-helper'
import {GitCommandManager} from './git-command-manager'
import {GitAuthHelper} from './git-auth-helper'
import {GitConfigHelper} from './git-config-helper'
import * as utils from './utils'
export interface Inputs {
@@ -35,48 +35,18 @@ export interface Inputs {
}
export async function createPullRequest(inputs: Inputs): Promise<void> {
let gitAuthHelper, git
let gitConfigHelper, git
try {
if (!inputs.token) {
throw new Error(`Input 'token' not supplied. Unable to continue.`)
}
if (inputs.bodyPath) {
if (!utils.fileExistsSync(inputs.bodyPath)) {
throw new Error(`File '${inputs.bodyPath}' does not exist.`)
}
// Update the body input with the contents of the file
inputs.body = utils.readFile(inputs.bodyPath)
}
// 65536 characters is the maximum allowed for the pull request body.
if (inputs.body.length > 65536) {
core.warning(
`Pull request body is too long. Truncating to 65536 characters.`
)
const truncateWarning = '...*[Pull request body truncated]*'
inputs.body =
inputs.body.substring(0, 65536 - truncateWarning.length) +
truncateWarning
}
// Get the repository path
const repoPath = utils.getRepoPath(inputs.path)
// Create a git command manager
git = await GitCommandManager.create(repoPath)
// Save and unset the extraheader auth config if it exists
core.startGroup('Prepare git configuration')
gitAuthHelper = new GitAuthHelper(git)
await gitAuthHelper.addSafeDirectory()
await gitAuthHelper.savePersistedAuth()
const repoPath = utils.getRepoPath(inputs.path)
git = await GitCommandManager.create(repoPath)
gitConfigHelper = await GitConfigHelper.create(git)
core.endGroup()
// 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
const remoteUrl = await git.tryGetRemoteUrl()
const baseRemote = utils.getRemoteDetail(remoteUrl)
const baseRemote = gitConfigHelper.getGitRemote()
// Init the GitHub client
const githubHelper = new GitHubHelper(baseRemote.hostname, inputs.token)
// Determine the head repository; the target for the pull request branch
const branchRemoteName = inputs.pushToFork ? 'fork' : 'origin'
const branchRepository = inputs.pushToFork
@@ -121,7 +91,7 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
// Configure auth
if (baseRemote.protocol == 'HTTPS') {
core.startGroup('Configuring credential for HTTPS authentication')
await gitAuthHelper.configureToken(inputs.gitToken)
await gitConfigHelper.configureToken(inputs.gitToken)
core.endGroup()
}
@@ -281,14 +251,11 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
} catch (error) {
core.setFailed(utils.getErrorMessage(error))
} finally {
// Remove auth and restore persisted auth config if it existed
core.startGroup('Restore git configuration')
if (inputs.pushToFork) {
await git.exec(['remote', 'rm', 'fork'])
}
await gitAuthHelper.removeAuth()
await gitAuthHelper.restorePersistedAuth()
await gitAuthHelper.removeSafeDirectory()
await gitConfigHelper.close()
core.endGroup()
}
}

View File

@@ -5,22 +5,42 @@ import * as path from 'path'
import {URL} from 'url'
import * as utils from './utils'
export class GitAuthHelper {
interface GitRemote {
hostname: string
protocol: string
repository: string
}
export class GitConfigHelper {
private git: GitCommandManager
private gitConfigPath = ''
private workingDirectory: string
private safeDirectoryConfigKey = 'safe.directory'
private safeDirectoryAdded = false
private extraheaderConfigKey: string
private remoteUrl = ''
private extraheaderConfigKey = ''
private extraheaderConfigPlaceholderValue = 'AUTHORIZATION: basic ***'
private extraheaderConfigValueRegex = '^AUTHORIZATION:'
private persistedExtraheaderConfigValue = ''
constructor(git: GitCommandManager) {
private constructor(git: GitCommandManager) {
this.git = git
this.workingDirectory = this.git.getWorkingDirectory()
const serverUrl = this.getServerUrl()
this.extraheaderConfigKey = `http.${serverUrl.origin}/.extraheader`
}
static async create(git: GitCommandManager): Promise<GitConfigHelper> {
const gitConfigHelper = new GitConfigHelper(git)
await gitConfigHelper.addSafeDirectory()
await gitConfigHelper.fetchRemoteDetail()
await gitConfigHelper.savePersistedAuth()
return gitConfigHelper
}
async close(): Promise<void> {
// Remove auth and restore persisted auth config if it existed
await this.removeAuth()
await this.restorePersistedAuth()
await this.removeSafeDirectory()
}
async addSafeDirectory(): Promise<void> {
@@ -50,7 +70,47 @@ export class GitAuthHelper {
}
}
async fetchRemoteDetail(): Promise<void> {
this.remoteUrl = await this.git.tryGetRemoteUrl()
}
getGitRemote(): GitRemote {
return GitConfigHelper.parseGitRemote(this.remoteUrl)
}
static parseGitRemote(remoteUrl: string): GitRemote {
const httpsUrlPattern = new RegExp(
'^(https?)://(?:.+@)?(.+?)/(.+/.+?)(\\.git)?$',
'i'
)
const sshUrlPattern = new RegExp('^git@(.+?):(.+/.+)\\.git$', 'i')
const httpsMatch = remoteUrl.match(httpsUrlPattern)
if (httpsMatch) {
return {
hostname: httpsMatch[2],
protocol: 'HTTPS',
repository: httpsMatch[3]
}
}
const sshMatch = remoteUrl.match(sshUrlPattern)
if (sshMatch) {
return {
hostname: sshMatch[1],
protocol: 'SSH',
repository: sshMatch[2]
}
}
throw new Error(
`The format of '${remoteUrl}' is not a valid GitHub repository URL`
)
}
async savePersistedAuth(): Promise<void> {
const serverUrl = new URL(`https://${this.getGitRemote().hostname}`)
this.extraheaderConfigKey = `http.${serverUrl.origin}/.extraheader`
// Save and unset persisted extraheader credential in git config if it exists
this.persistedExtraheaderConfigValue = await this.getAndUnset()
}
@@ -144,8 +204,4 @@ export class GitAuthHelper {
content = content.replace(find, replace)
await fs.promises.writeFile(this.gitConfigPath, content)
}
private getServerUrl(): URL {
return new URL(process.env['GITHUB_SERVER_URL'] || 'https://github.com')
}
}

View File

@@ -20,12 +20,16 @@ interface Pull {
export class GitHubHelper {
private octokit: InstanceType<typeof Octokit>
constructor(token: string) {
constructor(githubServerHostname: string, token: string) {
const options: OctokitOptions = {}
if (token) {
options.auth = `${token}`
}
options.baseUrl = process.env['GITHUB_API_URL'] || 'https://api.github.com'
if (githubServerHostname !== 'github.com') {
options.baseUrl = `https://${githubServerHostname}/api/v3`
} else {
options.baseUrl = 'https://api.github.com'
}
this.octokit = new Octokit(options)
}

View File

@@ -31,9 +31,26 @@ async function run(): Promise<void> {
}
core.debug(`Inputs: ${inspect(inputs)}`)
if (!inputs.token) {
throw new Error(`Input 'token' not supplied. Unable to continue.`)
}
if (!inputs.gitToken) {
inputs.gitToken = inputs.token
}
if (inputs.bodyPath) {
if (!utils.fileExistsSync(inputs.bodyPath)) {
throw new Error(`File '${inputs.bodyPath}' does not exist.`)
}
// Update the body input with the contents of the file
inputs.body = utils.readFile(inputs.bodyPath)
}
// 65536 characters is the maximum allowed for the pull request body.
if (inputs.body.length > 65536) {
core.warning(
`Pull request body is too long. Truncating to 65536 characters.`
)
inputs.body = inputs.body.substring(0, 65536)
}
await createPullRequest(inputs)
} catch (error) {

View File

@@ -41,53 +41,6 @@ export function getRepoPath(relativePath?: string): string {
return repoPath
}
interface RemoteDetail {
hostname: string
protocol: string
repository: string
}
export function getRemoteDetail(remoteUrl: string): RemoteDetail {
// Parse the protocol and github repository from a URL
// e.g. HTTPS, peter-evans/create-pull-request
const githubUrl = process.env['GITHUB_SERVER_URL'] || 'https://github.com'
const githubServerMatch = githubUrl.match(/^https?:\/\/(.+)$/i)
if (!githubServerMatch) {
throw new Error('Could not parse GitHub Server name')
}
const hostname = githubServerMatch[1]
const httpsUrlPattern = new RegExp(
'^https?://.*@?' + hostname + '/(.+/.+?)(\\.git)?$',
'i'
)
const sshUrlPattern = new RegExp('^git@' + hostname + ':(.+/.+)\\.git$', 'i')
const httpsMatch = remoteUrl.match(httpsUrlPattern)
if (httpsMatch) {
return {
hostname,
protocol: 'HTTPS',
repository: httpsMatch[1]
}
}
const sshMatch = remoteUrl.match(sshUrlPattern)
if (sshMatch) {
return {
hostname,
protocol: 'SSH',
repository: sshMatch[1]
}
}
throw new Error(
`The format of '${remoteUrl}' is not a valid GitHub repository URL`
)
}
export function getRemoteUrl(
protocol: string,
hostname: string,