2023-06-23 17:20:13 +00:00
import * as core from '@actions/core'
import * as github from '@actions/github'
import type { RestEndpointMethodTypes } from '@octokit/rest'
2023-07-18 09:44:59 +00:00
import flatten from 'lodash/flatten'
2023-08-22 03:11:59 +00:00
import mm from 'micromatch'
2023-05-25 18:22:24 +00:00
import * as path from 'path'
2023-09-18 20:17:46 +00:00
import { setOutputsAndGetModifiedAndChangedFilesStatus } from './changedFilesOutput'
2023-05-25 18:22:24 +00:00
import { DiffResult } from './commitSha'
import { Inputs } from './inputs'
import {
2023-08-23 04:26:07 +00:00
canDiffCommits ,
2023-07-18 09:44:59 +00:00
getAllChangedFiles ,
2023-05-25 18:22:24 +00:00
getDirnameMaxDepth ,
2023-08-22 03:11:59 +00:00
getDirNamesIncludeFilesPattern ,
2023-09-16 11:44:37 +00:00
getFilteredChangedFiles ,
2023-05-25 18:22:24 +00:00
gitRenamedFiles ,
gitSubmoduleDiffSHA ,
2023-08-22 03:11:59 +00:00
isWindows ,
2023-09-18 20:17:46 +00:00
jsonOutput ,
setOutput
2023-05-25 18:22:24 +00:00
} from './utils'
2023-09-16 11:44:37 +00:00
export const processChangedFiles = async ( {
filePatterns ,
allDiffFiles ,
inputs ,
yamlFilePatterns
} : {
filePatterns : string [ ]
allDiffFiles : ChangedFiles
inputs : Inputs
yamlFilePatterns : Record < string , string [ ] >
} ) : Promise < void > = > {
if ( filePatterns . length > 0 ) {
core . startGroup ( 'changed-files-patterns' )
const allFilteredDiffFiles = await getFilteredChangedFiles ( {
allDiffFiles ,
filePatterns
} )
core . debug (
` All filtered diff files: ${ JSON . stringify ( allFilteredDiffFiles ) } `
)
2023-09-18 20:17:46 +00:00
await setOutputsAndGetModifiedAndChangedFilesStatus ( {
2023-09-16 11:44:37 +00:00
allDiffFiles ,
allFilteredDiffFiles ,
inputs ,
filePatterns
} )
core . info ( 'All Done!' )
core . endGroup ( )
}
if ( Object . keys ( yamlFilePatterns ) . length > 0 ) {
2023-09-18 23:37:47 +00:00
const modifiedKeys : string [ ] = [ ]
const changedKeys : string [ ] = [ ]
2023-09-16 11:44:37 +00:00
for ( const key of Object . keys ( yamlFilePatterns ) ) {
core . startGroup ( ` changed-files-yaml- ${ key } ` )
const allFilteredDiffFiles = await getFilteredChangedFiles ( {
allDiffFiles ,
filePatterns : yamlFilePatterns [ key ]
} )
core . debug (
` All filtered diff files for ${ key } : ${ JSON . stringify (
allFilteredDiffFiles
) } `
)
2023-09-18 20:17:46 +00:00
const { anyChanged , anyModified } =
await setOutputsAndGetModifiedAndChangedFilesStatus ( {
allDiffFiles ,
allFilteredDiffFiles ,
inputs ,
filePatterns : yamlFilePatterns [ key ] ,
outputPrefix : key
} )
if ( anyModified ) {
modifiedKeys . push ( key )
}
if ( anyChanged ) {
changedKeys . push ( key )
}
2023-09-16 11:44:37 +00:00
core . info ( 'All Done!' )
core . endGroup ( )
}
2023-09-18 20:17:46 +00:00
if ( modifiedKeys . length > 0 ) {
await setOutput ( {
key : 'modified_keys' ,
value : modifiedKeys.join ( inputs . separator ) ,
writeOutputFiles : inputs.writeOutputFiles ,
outputDir : inputs.outputDir ,
json : inputs.json
} )
}
if ( changedKeys . length > 0 ) {
await setOutput ( {
key : 'changed_keys' ,
value : changedKeys.join ( inputs . separator ) ,
writeOutputFiles : inputs.writeOutputFiles ,
outputDir : inputs.outputDir ,
json : inputs.json
} )
}
2023-09-16 11:44:37 +00:00
}
if ( filePatterns . length === 0 && Object . keys ( yamlFilePatterns ) . length === 0 ) {
core . startGroup ( 'changed-files-all' )
2023-09-18 20:17:46 +00:00
await setOutputsAndGetModifiedAndChangedFilesStatus ( {
2023-09-16 11:44:37 +00:00
allDiffFiles ,
allFilteredDiffFiles : allDiffFiles ,
inputs
} )
core . info ( 'All Done!' )
core . endGroup ( )
}
}
2023-05-25 18:22:24 +00:00
export const getRenamedFiles = async ( {
inputs ,
workingDirectory ,
hasSubmodule ,
diffResult ,
submodulePaths
} : {
inputs : Inputs
workingDirectory : string
hasSubmodule : boolean
diffResult : DiffResult
submodulePaths : string [ ]
2023-06-17 03:13:40 +00:00
} ) : Promise < { paths : string ; count : string } > = > {
2023-05-25 18:22:24 +00:00
const renamedFiles = await gitRenamedFiles ( {
cwd : workingDirectory ,
sha1 : diffResult.previousSha ,
sha2 : diffResult.currentSha ,
diff : diffResult.diff ,
oldNewSeparator : inputs.oldNewSeparator
} )
if ( hasSubmodule ) {
for ( const submodulePath of submodulePaths ) {
const submoduleShaResult = await gitSubmoduleDiffSHA ( {
cwd : workingDirectory ,
parentSha1 : diffResult.previousSha ,
parentSha2 : diffResult.currentSha ,
submodulePath ,
diff : diffResult.diff
} )
const submoduleWorkingDirectory = path . join (
workingDirectory ,
submodulePath
)
if ( submoduleShaResult . currentSha && submoduleShaResult . previousSha ) {
2023-08-23 04:26:07 +00:00
let diff = '...'
if (
! ( await canDiffCommits ( {
cwd : submoduleWorkingDirectory ,
sha1 : submoduleShaResult.previousSha ,
sha2 : submoduleShaResult.currentSha ,
diff
} ) )
) {
2023-08-26 20:08:40 +00:00
let message = ` Unable to use three dot diff for: ${ submodulePath } submodule. Falling back to two dot diff. You can set 'fetch_additional_submodule_history: true' to fetch additional submodule history in order to use three dot diff `
2023-08-26 03:00:53 +00:00
if ( inputs . fetchSubmoduleHistory ) {
message = ` To fetch additional submodule history for: ${ submodulePath } you can increase history depth using 'fetch_depth' input `
}
2023-08-26 20:08:40 +00:00
core . info ( message )
2023-08-23 04:26:07 +00:00
diff = '..'
}
2023-05-25 18:22:24 +00:00
const submoduleRenamedFiles = await gitRenamedFiles ( {
cwd : submoduleWorkingDirectory ,
sha1 : submoduleShaResult.previousSha ,
sha2 : submoduleShaResult.currentSha ,
2023-08-23 04:26:07 +00:00
diff ,
2023-05-25 18:22:24 +00:00
oldNewSeparator : inputs.oldNewSeparator ,
isSubmodule : true ,
parentDir : submodulePath
} )
renamedFiles . push ( . . . submoduleRenamedFiles )
}
}
}
if ( inputs . json ) {
2023-06-17 03:13:40 +00:00
return {
paths : jsonOutput ( { value : renamedFiles , shouldEscape : inputs.escapeJson } ) ,
count : renamedFiles.length.toString ( )
}
2023-05-25 18:22:24 +00:00
}
2023-06-17 03:13:40 +00:00
return {
paths : renamedFiles.join ( inputs . oldNewFilesSeparator ) ,
count : renamedFiles.length.toString ( )
}
2023-05-25 18:22:24 +00:00
}
2023-06-14 18:45:32 +00:00
export enum ChangeTypeEnum {
Added = 'A' ,
Copied = 'C' ,
Deleted = 'D' ,
Modified = 'M' ,
Renamed = 'R' ,
TypeChanged = 'T' ,
Unmerged = 'U' ,
Unknown = 'X'
}
export type ChangedFiles = {
[ key in ChangeTypeEnum ] : string [ ]
}
export const getAllDiffFiles = async ( {
2023-05-25 18:22:24 +00:00
workingDirectory ,
hasSubmodule ,
diffResult ,
2023-06-14 19:59:31 +00:00
submodulePaths ,
2023-08-26 03:00:53 +00:00
outputRenamedFilesAsDeletedAndAdded ,
2023-08-30 20:51:36 +00:00
fetchSubmoduleHistory ,
failOnInitialDiffError ,
failOnSubmoduleDiffError
2023-05-25 18:22:24 +00:00
} : {
workingDirectory : string
hasSubmodule : boolean
diffResult : DiffResult
submodulePaths : string [ ]
2023-06-14 19:59:31 +00:00
outputRenamedFilesAsDeletedAndAdded : boolean
2023-08-26 03:00:53 +00:00
fetchSubmoduleHistory : boolean
2023-08-30 20:51:36 +00:00
failOnInitialDiffError : boolean
failOnSubmoduleDiffError : boolean
2023-06-14 18:45:32 +00:00
} ) : Promise < ChangedFiles > = > {
const files = await getAllChangedFiles ( {
2023-05-25 18:22:24 +00:00
cwd : workingDirectory ,
sha1 : diffResult.previousSha ,
sha2 : diffResult.currentSha ,
2023-06-14 19:59:31 +00:00
diff : diffResult.diff ,
2023-08-30 20:51:36 +00:00
outputRenamedFilesAsDeletedAndAdded ,
failOnInitialDiffError
2023-05-25 18:22:24 +00:00
} )
if ( hasSubmodule ) {
for ( const submodulePath of submodulePaths ) {
const submoduleShaResult = await gitSubmoduleDiffSHA ( {
cwd : workingDirectory ,
parentSha1 : diffResult.previousSha ,
parentSha2 : diffResult.currentSha ,
submodulePath ,
diff : diffResult.diff
} )
const submoduleWorkingDirectory = path . join (
workingDirectory ,
submodulePath
)
if ( submoduleShaResult . currentSha && submoduleShaResult . previousSha ) {
2023-08-23 04:26:07 +00:00
let diff = '...'
if (
! ( await canDiffCommits ( {
cwd : submoduleWorkingDirectory ,
sha1 : submoduleShaResult.previousSha ,
sha2 : submoduleShaResult.currentSha ,
diff
} ) )
) {
2023-08-26 03:00:53 +00:00
let message = ` Set 'fetch_additional_submodule_history: true' to fetch additional submodule history for: ${ submodulePath } `
if ( fetchSubmoduleHistory ) {
message = ` To fetch additional submodule history for: ${ submodulePath } you can increase history depth using 'fetch_depth' input `
}
core . warning ( message )
2023-08-23 04:26:07 +00:00
diff = '..'
}
2023-06-14 18:45:32 +00:00
const submoduleFiles = await getAllChangedFiles ( {
2023-05-25 18:22:24 +00:00
cwd : submoduleWorkingDirectory ,
sha1 : submoduleShaResult.previousSha ,
sha2 : submoduleShaResult.currentSha ,
2023-08-23 04:26:07 +00:00
diff ,
2023-05-25 18:22:24 +00:00
isSubmodule : true ,
2023-06-14 19:59:31 +00:00
parentDir : submodulePath ,
2023-08-30 20:51:36 +00:00
outputRenamedFilesAsDeletedAndAdded ,
failOnSubmoduleDiffError
2023-05-25 18:22:24 +00:00
} )
2023-06-14 18:45:32 +00:00
for ( const changeType of Object . keys (
submoduleFiles
) as ChangeTypeEnum [ ] ) {
if ( ! files [ changeType ] ) {
files [ changeType ] = [ ]
}
files [ changeType ] . push ( . . . submoduleFiles [ changeType ] )
}
2023-05-25 18:22:24 +00:00
}
}
}
2023-06-14 18:45:32 +00:00
return files
}
2023-08-22 03:11:59 +00:00
function * getFilePaths ( {
inputs ,
filePaths ,
dirNamesIncludeFilePatterns
} : {
inputs : Inputs
filePaths : string [ ]
dirNamesIncludeFilePatterns : string [ ]
} ) : Generator < string > {
for ( const filePath of filePaths ) {
if ( inputs . dirNames ) {
if ( dirNamesIncludeFilePatterns . length > 0 ) {
const isWin = isWindows ( )
const matchOptions = { dot : true , windows : isWin , noext : true }
if ( mm . isMatch ( filePath , dirNamesIncludeFilePatterns , matchOptions ) ) {
yield filePath
}
}
yield getDirnameMaxDepth ( {
relativePath : filePath ,
dirNamesMaxDepth : inputs.dirNamesMaxDepth ,
excludeCurrentDir : inputs.dirNamesExcludeCurrentDir
} )
} else {
yield filePath
}
}
}
2023-06-14 18:45:32 +00:00
function * getChangeTypeFilesGenerator ( {
inputs ,
changedFiles ,
changeTypes
} : {
inputs : Inputs
changedFiles : ChangedFiles
changeTypes : ChangeTypeEnum [ ]
} ) : Generator < string > {
2023-08-22 03:11:59 +00:00
const dirNamesIncludeFilePatterns = getDirNamesIncludeFilesPattern ( { inputs } )
core . debug (
` Dir names include file patterns: ${ JSON . stringify (
dirNamesIncludeFilePatterns
) } `
)
2023-06-14 18:45:32 +00:00
for ( const changeType of changeTypes ) {
2023-08-22 03:11:59 +00:00
const filePaths = changedFiles [ changeType ] || [ ]
for ( const filePath of getFilePaths ( {
inputs ,
filePaths ,
dirNamesIncludeFilePatterns
} ) ) {
yield filePath
2023-06-14 18:45:32 +00:00
}
}
}
export const getChangeTypeFiles = async ( {
inputs ,
changedFiles ,
changeTypes
} : {
inputs : Inputs
changedFiles : ChangedFiles
changeTypes : ChangeTypeEnum [ ]
2023-09-04 20:03:32 +00:00
} ) : Promise < { paths : string [ ] | string ; count : string } > = > {
2023-06-14 18:45:32 +00:00
const files = [
. . . new Set ( getChangeTypeFilesGenerator ( { inputs , changedFiles , changeTypes } ) )
2023-07-13 19:01:23 +00:00
] . filter ( Boolean )
2023-06-14 18:45:32 +00:00
2023-09-04 20:03:32 +00:00
const paths = inputs . json ? files : files.join ( inputs . separator )
2023-06-14 18:45:32 +00:00
2023-06-17 03:13:40 +00:00
return {
2023-09-04 20:03:32 +00:00
paths ,
2023-06-17 03:13:40 +00:00
count : files.length.toString ( )
}
2023-06-14 18:45:32 +00:00
}
function * getAllChangeTypeFilesGenerator ( {
inputs ,
changedFiles
} : {
inputs : Inputs
changedFiles : ChangedFiles
} ) : Generator < string > {
2023-08-22 03:11:59 +00:00
const dirNamesIncludeFilePatterns = getDirNamesIncludeFilesPattern ( { inputs } )
core . debug (
` Dir names include file patterns: ${ JSON . stringify (
dirNamesIncludeFilePatterns
) } `
)
const filePaths = flatten ( Object . values ( changedFiles ) )
for ( const filePath of getFilePaths ( {
inputs ,
filePaths ,
dirNamesIncludeFilePatterns
} ) ) {
yield filePath
2023-05-25 18:22:24 +00:00
}
2023-06-14 18:45:32 +00:00
}
export const getAllChangeTypeFiles = async ( {
inputs ,
changedFiles
} : {
inputs : Inputs
changedFiles : ChangedFiles
2023-09-04 20:03:32 +00:00
} ) : Promise < { paths : string [ ] | string ; count : string } > = > {
2023-06-14 18:45:32 +00:00
const files = [
. . . new Set ( getAllChangeTypeFilesGenerator ( { inputs , changedFiles } ) )
2023-07-13 19:01:23 +00:00
] . filter ( Boolean )
2023-05-25 18:22:24 +00:00
2023-09-04 20:03:32 +00:00
const paths = inputs . json ? files : files.join ( inputs . separator )
2023-05-25 18:22:24 +00:00
2023-06-17 03:13:40 +00:00
return {
2023-09-04 20:03:32 +00:00
paths ,
2023-06-17 03:13:40 +00:00
count : files.length.toString ( )
}
2023-05-25 18:22:24 +00:00
}
2023-06-23 17:20:13 +00:00
export const getChangedFilesFromGithubAPI = async ( {
2023-07-18 09:44:59 +00:00
inputs
2023-06-23 17:20:13 +00:00
} : {
inputs : Inputs
} ) : Promise < ChangedFiles > = > {
2023-06-23 18:55:55 +00:00
const octokit = github . getOctokit ( inputs . token , {
baseUrl : inputs.apiUrl
} )
2023-06-23 17:20:13 +00:00
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 ,
2023-07-18 09:44:59 +00:00
pull_number : github.context.payload.pull_request?.number ,
2023-06-23 17:20:13 +00:00
per_page : 100
} )
2023-08-15 17:37:46 +00:00
const paginatedResponse =
await octokit . paginate <
RestEndpointMethodTypes [ 'pulls' ] [ 'listFiles' ] [ 'response' ] [ 'data' ] [ 0 ]
> ( options )
2023-06-23 17:20:13 +00:00
2023-06-23 18:55:55 +00:00
core . info ( ` Found ${ paginatedResponse . length } changed files from GitHub API ` )
2023-06-23 17:20:13 +00:00
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 )
}
}
2023-08-22 03:11:59 +00:00
2023-06-23 17:20:13 +00:00
return changedFiles
}