Skip to content
Navigation Menu
Toggle navigation
Sign in
In this repository
All GitHub Enterprise
↵
Jump to
↵
No suggested jump to results
In this repository
All GitHub Enterprise
↵
Jump to
↵
In this organization
All GitHub Enterprise
↵
Jump to
↵
In this repository
All GitHub Enterprise
↵
Jump to
↵
Sign in
Reseting focus
You signed in with another tab or window.
Reload
to refresh your session.
You signed out in another tab or window.
Reload
to refresh your session.
You switched accounts on another tab or window.
Reload
to refresh your session.
Dismiss alert
{{ message }}
dorny
/
paths-filter
Public
Notifications
You must be signed in to change notification settings
Fork
0
Star
0
Code
Pull requests
0
Actions
Security
Insights
Additional navigation options
Code
Pull requests
Actions
Security
Insights
Files
6d81690
.github
.vscode
__tests__
dist
src
list-format
exec.ts
file.ts
filter.ts
git.ts
main.ts
.editorconfig
.eslintignore
.eslintrc.json
.gitattributes
.gitignore
.prettierignore
.prettierrc.json
CHANGELOG.md
LICENSE
README.md
action.yml
jest.config.js
package-lock.json
package.json
tsconfig.json
Breadcrumbs
paths-filter
/
src
/
main.ts
Blame
Blame
Latest commit
History
History
231 lines (202 loc) · 8.37 KB
Breadcrumbs
paths-filter
/
src
/
main.ts
Top
File metadata and controls
Code
Blame
231 lines (202 loc) · 8.37 KB
Raw
import * as fs from 'fs' import * as core from '@actions/core' import * as github from '@actions/github' import {Webhooks} from '@octokit/webhooks' import {Filter, FilterResults} from './filter' import {File, ChangeStatus} from './file' import * as git from './git' import {backslashEscape, shellEscape} from './list-format/shell-escape' import {csvEscape} from './list-format/csv-escape' type ExportFormat = 'none' | 'csv' | 'json' | 'shell' | 'escape' async function run(): Promise<void> { try { const workingDirectory = core.getInput('working-directory', {required: false}) if (workingDirectory) { process.chdir(workingDirectory) } const token = core.getInput('token', {required: false}) const base = core.getInput('base', {required: false}) const filtersInput = core.getInput('filters', {required: true}) const filtersYaml = isPathInput(filtersInput) ? getConfigFileContent(filtersInput) : filtersInput const listFiles = core.getInput('list-files', {required: false}).toLowerCase() || 'none' const initialFetchDepth = parseInt(core.getInput('initial-fetch-depth', {required: false})) || 10 if (!isExportFormat(listFiles)) { core.setFailed(`Input parameter 'list-files' is set to invalid value '${listFiles}'`) return } const filter = new Filter(filtersYaml) const files = await getChangedFiles(token, base, initialFetchDepth) const results = filter.match(files) exportResults(results, listFiles) } catch (error) { core.setFailed(error.message) } } function isPathInput(text: string): boolean { return !(text.includes('\n') || text.includes(':')) } function getConfigFileContent(configPath: string): string { if (!fs.existsSync(configPath)) { throw new Error(`Configuration file '${configPath}' not found`) } if (!fs.lstatSync(configPath).isFile()) { throw new Error(`'${configPath}' is not a file.`) } return fs.readFileSync(configPath, {encoding: 'utf8'}) } async function getChangedFiles(token: string, base: string, initialFetchDepth: number): Promise<File[]> { // if base is 'HEAD' only local uncommitted changes will be detected // This is the simplest case as we don't need to fetch more commits or evaluate current/before refs if (base === git.HEAD) { return await git.getChangesOnHead() } if (github.context.eventName === 'pull_request' || github.context.eventName === 'pull_request_target') { const pr = github.context.payload.pull_request as Webhooks.WebhookPayloadPullRequestPullRequest if (token) { return await getChangedFilesFromApi(token, pr) } core.info('Github token is not available - changes will be detected from PRs merge commit') return await git.getChangesInLastCommit() } else { return getChangedFilesFromGit(base, initialFetchDepth) } } async function getChangedFilesFromGit(base: string, initialFetchDepth: number): Promise<File[]> { const defaultRef = github.context.payload.repository?.default_branch const beforeSha = github.context.eventName === 'push' ? (github.context.payload as Webhooks.WebhookPayloadPush).before : null const ref = git.getShortName(github.context.ref) || (core.warning(`'ref' field is missing in event payload - using current branch, tag or commit SHA`), await git.getCurrentRef()) const baseRef = git.getShortName(base) || defaultRef if (!baseRef) { throw new Error( "This action requires 'base' input to be configured or 'repository.default_branch' to be set in the event payload" ) } const isBaseRefSha = git.isGitSha(baseRef) const isBaseRefSameAsRef = baseRef === ref // If base is commit SHA we will do comparison against the referenced commit // Or if base references same branch it was pushed to, we will do comparison against the previously pushed commit if (isBaseRefSha || isBaseRefSameAsRef) { if (!isBaseRefSha && !beforeSha) { core.warning(`'before' field is missing in event payload - changes will be detected from last commit`) return await git.getChangesInLastCommit() } const baseSha = isBaseRefSha ? baseRef : beforeSha // If there is no previously pushed commit, // we will do comparison against the default branch or return all as added if (baseSha === git.NULL_SHA) { if (defaultRef && baseRef !== defaultRef) { core.info(`First push of a branch detected - changes will be detected against the default branch ${defaultRef}`) return await git.getChangesSinceMergeBase(defaultRef, ref, initialFetchDepth) } else { core.info('Initial push detected - all files will be listed as added') return await git.listAllFilesAsAdded() } } core.info(`Changes will be detected against commit (${baseSha})`) return await git.getChanges(baseSha) } // Changes introduced by current branch against the base branch core.info(`Changes will be detected against ${baseRef}`) return await git.getChangesSinceMergeBase(baseRef, ref, initialFetchDepth) } // Uses github REST api to get list of files changed in PR async function getChangedFilesFromApi( token: string, pullRequest: Webhooks.WebhookPayloadPullRequestPullRequest ): Promise<File[]> { core.startGroup(`Fetching list of changed files for PR#${pullRequest.number} from Github API`) core.info(`Number of changed_files is ${pullRequest.changed_files}`) const client = new github.GitHub(token) const pageSize = 100 const files: File[] = [] for (let page = 1; (page - 1) * pageSize < pullRequest.changed_files; page++) { core.info(`Invoking listFiles(pull_number: ${pullRequest.number}, page: ${page}, per_page: ${pageSize})`) const response = await client.pulls.listFiles({ owner: github.context.repo.owner, repo: github.context.repo.repo, pull_number: pullRequest.number, page, per_page: pageSize }) for (const row of response.data) { core.info(`[${row.status}] ${row.filename}`) // There's no obvious use-case for detection of renames // Therefore we treat it as if rename detection in git diff was turned off. // Rename is replaced by delete of original filename and add of new filename if (row.status === ChangeStatus.Renamed) { files.push({ filename: row.filename, status: ChangeStatus.Added }) files.push({ // 'previous_filename' for some unknown reason isn't in the type definition or documentation filename: (<any>row).previous_filename as string, status: ChangeStatus.Deleted }) } else { // Github status and git status variants are same except for deleted files const status = row.status === 'removed' ? ChangeStatus.Deleted : (row.status as ChangeStatus) files.push({ filename: row.filename, status }) } } } core.endGroup() return files } function exportResults(results: FilterResults, format: ExportFormat): void { core.info('Results:') const changes = [] for (const [key, files] of Object.entries(results)) { const value = files.length > 0 core.startGroup(`Filter ${key} = ${value}`) if (files.length > 0) { changes.push(key) core.info('Matching files:') for (const file of files) { core.info(`${file.filename} [${file.status}]`) } } else { core.info('Matching files: none') } core.setOutput(key, value) core.setOutput(`${key}_count`, files.length) if (format !== 'none') { const filesValue = serializeExport(files, format) core.setOutput(`${key}_files`, filesValue) } core.endGroup() } if (results['changes'] === undefined) { const changesJson = JSON.stringify(changes) core.info(`Changes output set to ${changesJson}`) core.setOutput('changes', changesJson) } else { core.info('Cannot set changes output variable - name already used by filter output') } } function serializeExport(files: File[], format: ExportFormat): string { const fileNames = files.map(file => file.filename) switch (format) { case 'csv': return fileNames.map(csvEscape).join(',') case 'json': return JSON.stringify(fileNames) case 'escape': return fileNames.map(backslashEscape).join(' ') case 'shell': return fileNames.map(shellEscape).join(' ') default: return '' } } function isExportFormat(value: string): value is ExportFormat { return ['none', 'csv', 'shell', 'json', 'escape'].includes(value) } run()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
You can’t perform that action at this time.