feat: add support for retrieving changed files via github rest api (#1289)

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: tj-actions[bot] <109116665+tj-actions-bot@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
This commit is contained in:
Tonye Jack
2023-06-23 11:20:13 -06:00
committed by GitHub
parent c4a394a9cf
commit fd5b3a411d
14 changed files with 8436 additions and 215 deletions

View File

@@ -1,6 +1,10 @@
import * as core from '@actions/core'
import * as github from '@actions/github'
import type {RestEndpointMethodTypes} from '@octokit/rest'
import * as path from 'path'
import {DiffResult} from './commitSha'
import {Env} from './env'
import {Inputs} from './inputs'
import {
getDirnameMaxDepth,
@@ -248,3 +252,65 @@ export const getAllChangeTypeFiles = async ({
count: files.length.toString()
}
}
export const getChangedFilesFromGithubAPI = async ({
inputs,
env
}: {
inputs: Inputs
env: Env
}): Promise<ChangedFiles> => {
const octokit = github.getOctokit(inputs.token)
const changedFiles: ChangedFiles = {
[ChangeTypeEnum.Added]: [],
[ChangeTypeEnum.Copied]: [],
[ChangeTypeEnum.Deleted]: [],
[ChangeTypeEnum.Modified]: [],
[ChangeTypeEnum.Renamed]: [],
[ChangeTypeEnum.TypeChanged]: [],
[ChangeTypeEnum.Unmerged]: [],
[ChangeTypeEnum.Unknown]: []
}
core.info('Getting changed files from GitHub API...')
const options = octokit.rest.pulls.listFiles.endpoint.merge({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
pull_number: env.GITHUB_EVENT_PULL_REQUEST_NUMBER,
per_page: 100
})
const paginatedResponse = await octokit.paginate<
RestEndpointMethodTypes['pulls']['listFiles']['response']['data'][0]
>(options)
core.info(`Got ${paginatedResponse.length} changed files from GitHub API`)
const statusMap: Record<string, ChangeTypeEnum> = {
added: ChangeTypeEnum.Added,
removed: ChangeTypeEnum.Deleted,
modified: ChangeTypeEnum.Modified,
renamed: ChangeTypeEnum.Renamed,
copied: ChangeTypeEnum.Copied,
changed: ChangeTypeEnum.TypeChanged,
unchanged: ChangeTypeEnum.Unmerged
}
for await (const item of paginatedResponse) {
const changeType: ChangeTypeEnum =
statusMap[item.status] || ChangeTypeEnum.Unknown
if (changeType === ChangeTypeEnum.Renamed) {
if (inputs.outputRenamedFilesAsDeletedAndAdded) {
changedFiles[ChangeTypeEnum.Deleted].push(item.filename)
changedFiles[ChangeTypeEnum.Added].push(item.filename)
} else {
changedFiles[ChangeTypeEnum.Renamed].push(item.filename)
}
} else {
changedFiles[changeType].push(item.filename)
}
}
return changedFiles
}

View File

@@ -22,10 +22,10 @@ export const setChangedFilesOutput = async ({
outputPrefix = ''
}: {
allDiffFiles: ChangedFiles
filePatterns?: string[]
inputs: Inputs
workingDirectory: string
diffResult: DiffResult
diffResult?: DiffResult
filePatterns?: string[]
outputPrefix?: string
}): Promise<void> => {
const allFilteredDiffFiles = await getFilteredChangedFiles({
@@ -34,12 +34,14 @@ export const setChangedFilesOutput = async ({
})
core.debug(`All filtered diff files: ${JSON.stringify(allFilteredDiffFiles)}`)
await recoverDeletedFiles({
inputs,
workingDirectory,
deletedFiles: allFilteredDiffFiles[ChangeTypeEnum.Deleted],
sha: diffResult.previousSha
})
if (diffResult) {
await recoverDeletedFiles({
inputs,
workingDirectory,
deletedFiles: allFilteredDiffFiles[ChangeTypeEnum.Deleted],
sha: diffResult.previousSha
})
}
const addedFiles = await getChangeTypeFiles({
inputs,

View File

@@ -17,6 +17,8 @@ export type Env = {
GITHUB_EVENT_PULL_REQUEST_HEAD_SHA: string
GITHUB_EVENT_PULL_REQUEST_HEAD_REF: string
GITHUB_EVENT_PULL_REQUEST_BASE_REF: string
GITHUB_REPOSITORY_OWNER: string
GITHUB_REPOSITORY: string
}
type GithubEvent = {
@@ -71,6 +73,8 @@ export const getEnv = async (): Promise<Env> => {
GITHUB_REF_NAME: process.env.GITHUB_REF_NAME || '',
GITHUB_REF: process.env.GITHUB_REF || '',
GITHUB_WORKSPACE: process.env.GITHUB_WORKSPACE || '',
GITHUB_EVENT_NAME: process.env.GITHUB_EVENT_NAME || ''
GITHUB_EVENT_NAME: process.env.GITHUB_EVENT_NAME || '',
GITHUB_REPOSITORY_OWNER: process.env.GITHUB_REPOSITORY_OWNER || '',
GITHUB_REPOSITORY: process.env.GITHUB_REPOSITORY || ''
}
}

View File

@@ -39,6 +39,8 @@ export type Inputs = {
outputRenamedFilesAsDeletedAndAdded: boolean
recoverDeletedFiles: boolean
recoverDeletedFilesToDestination: string
token: string
api_url: string
}
export const getInputs = (): Inputs => {
@@ -154,6 +156,8 @@ export const getInputs = (): Inputs => {
'recover_deleted_files_to_destination',
{required: false}
)
const token = core.getInput('token', {required: false})
const api_url = core.getInput('api_url', {required: false})
const inputs: Inputs = {
files,
@@ -171,9 +175,7 @@ export const getInputs = (): Inputs => {
filesIgnoreYamlFromSourceFile,
filesIgnoreYamlFromSourceFileSeparator,
separator,
includeAllOldNewRenamedFiles,
oldNewSeparator,
oldNewFilesSeparator,
// Not Supported via REST API
sha,
baseSha,
since,
@@ -181,17 +183,23 @@ export const getInputs = (): Inputs => {
path,
quotePath,
diffRelative,
sinceLastRemoteCommit,
recoverDeletedFiles,
recoverDeletedFilesToDestination,
includeAllOldNewRenamedFiles,
oldNewSeparator,
oldNewFilesSeparator,
// End Not Supported via REST API
dirNames,
dirNamesExcludeRoot,
dirNamesExcludeCurrentDir,
json,
escapeJson,
sinceLastRemoteCommit,
writeOutputFiles,
outputDir,
outputRenamedFilesAsDeletedAndAdded,
recoverDeletedFiles,
recoverDeletedFilesToDestination
token,
api_url
}
if (fetchDepth) {

View File

@@ -1,18 +1,23 @@
import * as core from '@actions/core'
import path from 'path'
import {getAllDiffFiles, getRenamedFiles} from './changedFiles'
import {
getAllDiffFiles,
getChangedFilesFromGithubAPI,
getRenamedFiles
} from './changedFiles'
import {setChangedFilesOutput} from './changedFilesOutput'
import {
DiffResult,
getSHAForPullRequestEvent,
getSHAForPushEvent
} from './commitSha'
import {getEnv} from './env'
import {getInputs} from './inputs'
import {Env, getEnv} from './env'
import {getInputs, Inputs} from './inputs'
import {
getFilePatterns,
getSubmodulePath,
getYamlFilePatterns,
hasLocalGitDirectory,
isRepoShallow,
setOutput,
submoduleExists,
@@ -20,14 +25,15 @@ import {
verifyMinimumGitVersion
} from './utils'
export async function run(): Promise<void> {
core.startGroup('changed-files')
const env = await getEnv()
core.debug(`Env: ${JSON.stringify(env, null, 2)}`)
const inputs = getInputs()
core.debug(`Inputs: ${JSON.stringify(inputs, null, 2)}`)
const getChangedFilesFromLocalGit = async ({
inputs,
env,
workingDirectory
}: {
inputs: Inputs
env: Env
workingDirectory: string
}): Promise<void> => {
await verifyMinimumGitVersion()
let quotePathValue = 'on'
@@ -48,10 +54,6 @@ export async function run(): Promise<void> {
})
}
const workingDirectory = path.resolve(
env.GITHUB_WORKSPACE || process.cwd(),
inputs.path
)
const isShallow = await isRepoShallow({cwd: workingDirectory})
const hasSubmodule = await submoduleExists({cwd: workingDirectory})
let gitFetchExtraArgs = ['--no-tags', '--prune', '--recurse-submodules']
@@ -196,6 +198,124 @@ export async function run(): Promise<void> {
}
}
const getChangedFilesFromRESTAPI = async ({
inputs,
env,
workingDirectory
}: {
inputs: Inputs
env: Env
workingDirectory: string
}): Promise<void> => {
const allDiffFiles = await getChangedFilesFromGithubAPI({
inputs,
env
})
core.debug(`All diff files: ${JSON.stringify(allDiffFiles)}`)
core.info('All Done!')
const filePatterns = await getFilePatterns({
inputs,
workingDirectory
})
core.debug(`File patterns: ${filePatterns}`)
if (filePatterns.length > 0) {
core.startGroup('changed-files-patterns')
await setChangedFilesOutput({
allDiffFiles,
filePatterns,
inputs,
workingDirectory
})
core.info('All Done!')
core.endGroup()
}
const yamlFilePatterns = await getYamlFilePatterns({
inputs,
workingDirectory
})
core.debug(`Yaml file patterns: ${JSON.stringify(yamlFilePatterns)}`)
if (Object.keys(yamlFilePatterns).length > 0) {
for (const key of Object.keys(yamlFilePatterns)) {
core.startGroup(`changed-files-yaml-${key}`)
await setChangedFilesOutput({
allDiffFiles,
filePatterns: yamlFilePatterns[key],
outputPrefix: key,
inputs,
workingDirectory
})
core.info('All Done!')
core.endGroup()
}
}
if (filePatterns.length === 0 && Object.keys(yamlFilePatterns).length === 0) {
core.startGroup('changed-files-all')
await setChangedFilesOutput({
allDiffFiles,
inputs,
workingDirectory
})
core.info('All Done!')
core.endGroup()
}
}
export async function run(): Promise<void> {
core.startGroup('changed-files')
const env = await getEnv()
core.debug(`Env: ${JSON.stringify(env, null, 2)}`)
const inputs = getInputs()
core.debug(`Inputs: ${JSON.stringify(inputs, null, 2)}`)
const workingDirectory = path.resolve(
env.GITHUB_WORKSPACE || process.cwd(),
inputs.path
)
const hasGitDirectory = await hasLocalGitDirectory({workingDirectory})
if (
inputs.token &&
env.GITHUB_EVENT_PULL_REQUEST_NUMBER &&
!hasGitDirectory
) {
core.info("Using GitHub's REST API to get changed files")
const unsupportedInputs: (keyof Inputs)[] = [
'sha',
'baseSha',
'since',
'until',
'sinceLastRemoteCommit',
'recoverDeletedFiles',
'recoverDeletedFilesToDestination',
'includeAllOldNewRenamedFiles'
]
for (const input of unsupportedInputs) {
if (inputs[input]) {
core.warning(
`Input "${input}" is not supported when using GitHub's REST API to get changed files`
)
}
}
await getChangedFilesFromRESTAPI({inputs, env, workingDirectory})
} else {
if (!hasGitDirectory) {
core.setFailed(
"Can't find local .git directory. Please run actions/checkout before this action"
)
return
}
core.info('Using local .git directory')
await getChangedFilesFromLocalGit({inputs, env, workingDirectory})
}
}
/* istanbul ignore if */
if (!process.env.TESTING) {
// eslint-disable-next-line github/no-then

View File

@@ -97,7 +97,7 @@ export const verifyMinimumGitVersion = async (): Promise<void> => {
const {exitCode, stdout, stderr} = await exec.getExecOutput(
'git',
['--version'],
{silent: process.env.RUNNER_DEBUG !== '1'}
{silent: !core.isDebug()}
)
if (exitCode !== 0) {
@@ -181,7 +181,7 @@ export const updateGitGlobalConfig = async ({
['config', '--global', name, value],
{
ignoreReturnCode: true,
silent: process.env.RUNNER_DEBUG !== '1'
silent: !core.isDebug()
}
)
@@ -197,7 +197,7 @@ export const isRepoShallow = async ({cwd}: {cwd: string}): Promise<boolean> => {
['rev-parse', '--is-shallow-repository'],
{
cwd,
silent: process.env.RUNNER_DEBUG !== '1'
silent: !core.isDebug()
}
)
@@ -215,7 +215,7 @@ export const submoduleExists = async ({
{
cwd,
ignoreReturnCode: true,
silent: process.env.RUNNER_DEBUG !== '1'
silent: !core.isDebug()
}
)
@@ -236,7 +236,7 @@ export const gitFetch = async ({
const {exitCode} = await exec.getExecOutput('git', ['fetch', '-q', ...args], {
cwd,
ignoreReturnCode: true,
silent: process.env.RUNNER_DEBUG !== '1'
silent: !core.isDebug()
})
return exitCode
@@ -255,7 +255,7 @@ export const gitFetchSubmodules = async ({
{
cwd,
ignoreReturnCode: true,
silent: process.env.RUNNER_DEBUG !== '1'
silent: !core.isDebug()
}
)
@@ -280,7 +280,7 @@ export const getSubmodulePath = async ({
{
cwd,
ignoreReturnCode: true,
silent: process.env.RUNNER_DEBUG !== '1'
silent: !core.isDebug()
}
)
@@ -313,7 +313,7 @@ export const gitSubmoduleDiffSHA = async ({
['diff', parentSha1, parentSha2, '--', submodulePath],
{
cwd,
silent: process.env.RUNNER_DEBUG !== '1'
silent: !core.isDebug()
}
)
@@ -366,7 +366,7 @@ export const gitRenamedFiles = async ({
{
cwd,
ignoreReturnCode: true,
silent: process.env.RUNNER_DEBUG !== '1'
silent: !core.isDebug()
}
)
@@ -436,7 +436,7 @@ export const getAllChangedFiles = async ({
{
cwd,
ignoreReturnCode: true,
silent: process.env.RUNNER_DEBUG !== '1'
silent: !core.isDebug()
}
)
const changedFiles: ChangedFiles = {
@@ -537,7 +537,7 @@ export const gitLog = async ({
}): Promise<string> => {
const {stdout} = await exec.getExecOutput('git', ['log', ...args], {
cwd,
silent: process.env.RUNNER_DEBUG !== '1'
silent: !core.isDebug()
})
return stdout.trim()
@@ -546,7 +546,7 @@ export const gitLog = async ({
export const getHeadSha = async ({cwd}: {cwd: string}): Promise<string> => {
const {stdout} = await exec.getExecOutput('git', ['rev-parse', 'HEAD'], {
cwd,
silent: process.env.RUNNER_DEBUG !== '1'
silent: !core.isDebug()
})
return stdout.trim()
@@ -564,7 +564,7 @@ export const getRemoteBranchHeadSha = async ({
['rev-parse', `origin/${branch}`],
{
cwd,
silent: process.env.RUNNER_DEBUG !== '1'
silent: !core.isDebug()
}
)
@@ -578,7 +578,7 @@ export const getParentSha = async ({cwd}: {cwd: string}): Promise<string> => {
{
cwd,
ignoreReturnCode: true,
silent: process.env.RUNNER_DEBUG !== '1'
silent: !core.isDebug()
}
)
@@ -604,7 +604,7 @@ export const verifyCommitSha = async ({
{
cwd,
ignoreReturnCode: true,
silent: process.env.RUNNER_DEBUG !== '1'
silent: !core.isDebug()
}
)
@@ -634,7 +634,7 @@ export const getPreviousGitTag = async ({
['tag', '--sort=-version:refname'],
{
cwd,
silent: process.env.RUNNER_DEBUG !== '1'
silent: !core.isDebug()
}
)
@@ -652,7 +652,7 @@ export const getPreviousGitTag = async ({
['rev-parse', previousTag],
{
cwd,
silent: process.env.RUNNER_DEBUG !== '1'
silent: !core.isDebug()
}
)
@@ -678,7 +678,7 @@ export const canDiffCommits = async ({
{
cwd,
ignoreReturnCode: true,
silent: process.env.RUNNER_DEBUG !== '1'
silent: !core.isDebug()
}
)
@@ -1047,7 +1047,7 @@ const getDeletedFileContents = async ({
['show', `${sha}:${filePath}`],
{
cwd,
silent: process.env.RUNNER_DEBUG !== '1',
silent: !core.isDebug(),
ignoreReturnCode: true
}
)
@@ -1097,3 +1097,12 @@ export const recoverDeletedFiles = async ({
}
}
}
export const hasLocalGitDirectory = async ({
workingDirectory
}: {
workingDirectory: string
}): Promise<boolean> => {
const gitDirectory = path.join(workingDirectory, '.git')
return await exists(gitDirectory)
}