* feat: restore working base branch and uncommitted changes

* docs: uncommitted changes are stashed and restored

* docs: add major version notes

* fix: update package version

* fix: update package-lock

* feat: revise proxy implementation

* docs: add notes for the revised proxy implementation

* feat: set and remove git safe directory

* docs: add notes for the git safe directory feature

* fix: use base url for proxy check

* feat: determine the git dir with rev-parse

* build: update package lock

* fix: remove support for ghes alpha

* feat: revise handling of team reviewers

* docs: update notes

* feat: body-path

* docs: update to v5

* docs: update to v5

* build: fix package lock
This commit is contained in:
Peter Evans 2023-04-05 08:41:18 +09:00 committed by GitHub
parent 1847e5d1d6
commit 5b4a9f6a9e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 501 additions and 59041 deletions

View file

@ -106,6 +106,13 @@ function splitLines(multilineString: string): string[] {
.filter(x => x !== '')
}
interface CreateOrUpdateBranchResult {
action: string
base: string
hasDiffWithBase: boolean
headSha: string
}
export async function createOrUpdateBranch(
git: GitCommandManager,
commitMessage: string,
@ -163,9 +170,8 @@ export async function createOrUpdateBranch(
}
}
// Remove uncommitted tracked and untracked changes
await git.exec(['reset', '--hard'])
await git.exec(['clean', '-f', '-d'])
// Stash any uncommitted tracked and untracked changes
const stashed = await git.stashPush(['--include-untracked'])
// Perform fetch and reset the working base
// Commits made during the workflow will be removed
@ -283,12 +289,13 @@ export async function createOrUpdateBranch(
// Delete the temporary branch
await git.exec(['branch', '--delete', '--force', tempBranch])
// Checkout the working base to leave the local repository as it was found
await git.checkout(workingBase)
// Restore any stashed changes
if (stashed) {
await git.stashPop()
}
return result
}
interface CreateOrUpdateBranchResult {
action: string
base: string
hasDiffWithBase: boolean
headSha: string
}

View file

@ -24,6 +24,7 @@ export interface Inputs {
pushToFork: string
title: string
body: string
bodyPath: string
labels: string[]
assignees: string[]
reviewers: string[]
@ -38,6 +39,13 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
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)
}
// Get the repository path
const repoPath = utils.getRepoPath(inputs.path)
@ -45,8 +53,9 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
const git = await GitCommandManager.create(repoPath)
// Save and unset the extraheader auth config if it exists
core.startGroup('Save persisted git credentials')
core.startGroup('Prepare git configuration')
gitAuthHelper = new GitAuthHelper(git)
await gitAuthHelper.addSafeDirectory()
await gitAuthHelper.savePersistedAuth()
core.endGroup()
@ -195,7 +204,7 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
await git.push([
'--force-with-lease',
branchRemoteName,
`HEAD:refs/heads/${inputs.branch}`
`${inputs.branch}:refs/heads/${inputs.branch}`
])
core.endGroup()
}
@ -252,9 +261,10 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
core.setFailed(utils.getErrorMessage(error))
} finally {
// Remove auth and restore persisted auth config if it existed
core.startGroup('Restore persisted git credentials')
core.startGroup('Restore git configuration')
await gitAuthHelper.removeAuth()
await gitAuthHelper.restorePersistedAuth()
await gitAuthHelper.removeSafeDirectory()
core.endGroup()
}
}

View file

@ -7,7 +7,10 @@ import * as utils from './utils'
export class GitAuthHelper {
private git: GitCommandManager
private gitConfigPath: string
private gitConfigPath = ''
private workingDirectory: string
private safeDirectoryConfigKey = 'safe.directory'
private safeDirectoryAdded = false
private extraheaderConfigKey: string
private extraheaderConfigPlaceholderValue = 'AUTHORIZATION: basic ***'
private extraheaderConfigValueRegex = '^AUTHORIZATION:'
@ -15,15 +18,38 @@ export class GitAuthHelper {
constructor(git: GitCommandManager) {
this.git = git
this.gitConfigPath = path.join(
this.git.getWorkingDirectory(),
'.git',
'config'
)
this.workingDirectory = this.git.getWorkingDirectory()
const serverUrl = this.getServerUrl()
this.extraheaderConfigKey = `http.${serverUrl.origin}/.extraheader`
}
async addSafeDirectory(): Promise<void> {
const exists = await this.git.configExists(
this.safeDirectoryConfigKey,
this.workingDirectory,
true
)
if (!exists) {
await this.git.config(
this.safeDirectoryConfigKey,
this.workingDirectory,
true,
true
)
this.safeDirectoryAdded = true
}
}
async removeSafeDirectory(): Promise<void> {
if (this.safeDirectoryAdded) {
await this.git.tryConfigUnset(
this.safeDirectoryConfigKey,
this.workingDirectory,
true
)
}
}
async savePersistedAuth(): Promise<void> {
// Save and unset persisted extraheader credential in git config if it exists
this.persistedExtraheaderConfigValue = await this.getAndUnset()
@ -106,6 +132,10 @@ export class GitAuthHelper {
find: string,
replace: string
): Promise<void> {
if (this.gitConfigPath.length === 0) {
const gitDir = await this.git.getGitDirectory()
this.gitConfigPath = path.join(this.workingDirectory, gitDir, 'config')
}
let content = (await fs.promises.readFile(this.gitConfigPath)).toString()
const index = content.indexOf(find)
if (index < 0 || index != content.lastIndexOf(find)) {
@ -116,12 +146,6 @@ export class GitAuthHelper {
}
private getServerUrl(): URL {
// todo: remove GITHUB_URL after support for GHES Alpha is no longer needed
// See https://github.com/actions/checkout/blob/main/src/url-helper.ts#L22-L29
return new URL(
process.env['GITHUB_SERVER_URL'] ||
process.env['GITHUB_URL'] ||
'https://github.com'
)
return new URL(process.env['GITHUB_SERVER_URL'] || 'https://github.com')
}
}

View file

@ -72,14 +72,15 @@ export class GitCommandManager {
async config(
configKey: string,
configValue: string,
globalConfig?: boolean
globalConfig?: boolean,
add?: boolean
): Promise<void> {
await this.exec([
'config',
globalConfig ? '--global' : '--local',
configKey,
configValue
])
const args: string[] = ['config', globalConfig ? '--global' : '--local']
if (add) {
args.push('--add')
}
args.push(...[configKey, configValue])
await this.exec(args)
}
async configExists(
@ -145,6 +146,10 @@ export class GitCommandManager {
return output.stdout.trim().split(`${configKey} `)[1]
}
getGitDirectory(): Promise<string> {
return this.revParse('--git-dir')
}
getWorkingDirectory(): string {
return this.workingDirectory
}
@ -210,6 +215,23 @@ export class GitCommandManager {
return output.stdout.trim()
}
async stashPush(options?: string[]): Promise<boolean> {
const args = ['stash', 'push']
if (options) {
args.push(...options)
}
const output = await this.exec(args)
return output.stdout.trim() !== 'No local changes to save'
}
async stashPop(options?: string[]): Promise<void> {
const args = ['stash', 'pop']
if (options) {
args.push(...options)
}
await this.exec(args)
}
async status(options?: string[]): Promise<string> {
const args = ['status']
if (options) {

View file

@ -3,8 +3,8 @@ import {Inputs} from './create-pull-request'
import {Octokit, OctokitOptions} from './octokit-client'
import * as utils from './utils'
const ERROR_PR_REVIEW_FROM_AUTHOR =
'Review cannot be requested from pull request author'
const ERROR_PR_REVIEW_TOKEN_SCOPE =
'Validation Failed: "Could not resolve to a node with the global id of'
interface Repository {
owner: string
@ -159,8 +159,9 @@ export class GitHubHelper {
core.info(`Requesting reviewers '${inputs.reviewers}'`)
}
if (inputs.teamReviewers.length > 0) {
requestReviewersParams['team_reviewers'] = inputs.teamReviewers
core.info(`Requesting team reviewers '${inputs.teamReviewers}'`)
const teams = utils.stripOrgPrefixFromTeams(inputs.teamReviewers)
requestReviewersParams['team_reviewers'] = teams
core.info(`Requesting team reviewers '${teams}'`)
}
if (Object.keys(requestReviewersParams).length > 0) {
try {
@ -170,11 +171,12 @@ export class GitHubHelper {
...requestReviewersParams
})
} catch (e) {
if (utils.getErrorMessage(e).includes(ERROR_PR_REVIEW_FROM_AUTHOR)) {
core.warning(ERROR_PR_REVIEW_FROM_AUTHOR)
} else {
throw e
if (utils.getErrorMessage(e).includes(ERROR_PR_REVIEW_TOKEN_SCOPE)) {
core.error(
`Unable to request reviewers. If requesting team reviewers a 'repo' scoped PAT is required.`
)
}
throw e
}
}

View file

@ -20,6 +20,7 @@ async function run(): Promise<void> {
pushToFork: core.getInput('push-to-fork'),
title: core.getInput('title'),
body: core.getInput('body'),
bodyPath: core.getInput('body-path'),
labels: utils.getInputAsArray('labels'),
assignees: utils.getInputAsArray('assignees'),
reviewers: utils.getInputAsArray('reviewers'),

View file

@ -1,7 +1,8 @@
import {Octokit as Core} from '@octokit/core'
import {paginateRest} from '@octokit/plugin-paginate-rest'
import {restEndpointMethods} from '@octokit/plugin-rest-endpoint-methods'
import ProxyAgent from 'proxy-agent'
import {HttpsProxyAgent} from 'https-proxy-agent'
import {getProxyForUrl} from 'proxy-from-env'
export {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods'
export {OctokitOptions} from '@octokit/core/dist-types/types'
@ -13,16 +14,10 @@ export const Octokit = Core.plugin(
// Octokit plugin to support the standard environment variables http_proxy, https_proxy and no_proxy
function autoProxyAgent(octokit: Core) {
const proxy =
process.env.https_proxy ||
process.env.HTTPS_PROXY ||
process.env.http_proxy ||
process.env.HTTP_PROXY
if (!proxy) return
const agent = new ProxyAgent()
octokit.hook.before('request', options => {
options.request.agent = agent
const proxy = getProxyForUrl(options.baseUrl)
if (proxy) {
options.request.agent = new HttpsProxyAgent(proxy)
}
})
}

View file

@ -16,6 +16,16 @@ export function getStringAsArray(str: string): string[] {
.filter(x => x !== '')
}
export function stripOrgPrefixFromTeams(teams: string[]): string[] {
return teams.map(team => {
const slashIndex = team.lastIndexOf('/')
if (slashIndex > 0) {
return team.substring(slashIndex + 1)
}
return team
})
}
export function getRepoPath(relativePath?: string): string {
let githubWorkspacePath = process.env['GITHUB_WORKSPACE']
if (!githubWorkspacePath) {
@ -159,6 +169,10 @@ export function fileExistsSync(path: string): boolean {
return false
}
export function readFile(path: string): string {
return fs.readFileSync(path, 'utf-8')
}
/* eslint-disable @typescript-eslint/no-explicit-any */
function hasErrorCode(error: any): error is {code: string} {
return typeof (error && error.code) === 'string'