-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add HashFiles to the toolkit #830
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
0e0e9db
add hash files to the toolkit
thboop d3fd3dc
slight formatting updates
thboop 6b1b93f
create file then symdir
thboop 7e64601
split out tests
thboop 52c5a70
fix lint!
thboop 7cb8a4c
fix tests on ubuntu
thboop b5dfd33
minor updates
thboop 442ffe8
run linter
thboop 656d504
add multipath test
thboop b96cb32
change to warning
thboop File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| import * as io from '../../io/src/io' | ||
| import * as path from 'path' | ||
| import {hashFiles} from '../src/glob' | ||
| import {promises as fs} from 'fs' | ||
|
|
||
| const IS_WINDOWS = process.platform === 'win32' | ||
|
|
||
| /** | ||
| * These test focus on the ability of globber to find files | ||
| * and not on the pattern matching aspect | ||
| */ | ||
| describe('globber', () => { | ||
| beforeAll(async () => { | ||
| await io.rmRF(getTestTemp()) | ||
| }) | ||
|
|
||
| it('basic hashfiles test', async () => { | ||
| const root = path.join(getTestTemp(), 'basic-hashfiles') | ||
| await fs.mkdir(path.join(root), {recursive: true}) | ||
| await fs.writeFile(path.join(root, 'test.txt'), 'test file content') | ||
| const hash = await hashFiles(`${root}/*`) | ||
| expect(hash).toEqual( | ||
| 'd8a411e8f8643821bed189e627ff57151918aa554c00c10b31c693ab2dded273' | ||
| ) | ||
| }) | ||
|
|
||
| it('basic hashfiles no match should return empty string', async () => { | ||
| const root = path.join(getTestTemp(), 'empty-hashfiles') | ||
| const hash = await hashFiles(`${root}/*`) | ||
| expect(hash).toEqual('') | ||
| }) | ||
|
|
||
| it('followSymbolicLinks defaults to true', async () => { | ||
| const root = path.join( | ||
| getTestTemp(), | ||
| 'defaults-to-follow-symbolic-links-true' | ||
| ) | ||
| await fs.mkdir(path.join(root, 'realdir'), {recursive: true}) | ||
| await fs.writeFile( | ||
| path.join(root, 'realdir', 'file.txt'), | ||
| 'test file content' | ||
| ) | ||
| await createSymlinkDir( | ||
| path.join(root, 'realdir'), | ||
| path.join(root, 'symDir') | ||
| ) | ||
| const testPath = path.join(root, `symDir`) | ||
| const hash = await hashFiles(testPath) | ||
| expect(hash).toEqual( | ||
| 'd8a411e8f8643821bed189e627ff57151918aa554c00c10b31c693ab2dded273' | ||
| ) | ||
| }) | ||
|
|
||
| it('followSymbolicLinks set to true', async () => { | ||
| const root = path.join(getTestTemp(), 'set-to-true') | ||
| await fs.mkdir(path.join(root, 'realdir'), {recursive: true}) | ||
| await fs.writeFile(path.join(root, 'realdir', 'file'), 'test file content') | ||
| await createSymlinkDir( | ||
| path.join(root, 'realdir'), | ||
| path.join(root, 'symDir') | ||
| ) | ||
| const testPath = path.join(root, `symDir`) | ||
| const hash = await hashFiles(testPath, {followSymbolicLinks: true}) | ||
| expect(hash).toEqual( | ||
| 'd8a411e8f8643821bed189e627ff57151918aa554c00c10b31c693ab2dded273' | ||
| ) | ||
| }) | ||
|
|
||
| it('followSymbolicLinks set to false', async () => { | ||
| // Create the following layout: | ||
| // <root> | ||
| // <root>/folder-a | ||
| // <root>/folder-a/file | ||
| // <root>/symDir -> <root>/folder-a | ||
| const root = path.join(getTestTemp(), 'set-to-false') | ||
| await fs.mkdir(path.join(root, 'realdir'), {recursive: true}) | ||
| await fs.writeFile(path.join(root, 'realdir', 'file'), 'test file content') | ||
| await createSymlinkDir( | ||
| path.join(root, 'realdir'), | ||
| path.join(root, 'symDir') | ||
| ) | ||
| const testPath = path.join(root, 'symdir') | ||
| const hash = await hashFiles(testPath, {followSymbolicLinks: false}) | ||
| expect(hash).toEqual('') | ||
| }) | ||
|
|
||
| it('multipath test basic', async () => { | ||
| // Create the following layout: | ||
| // <root> | ||
| // <root>/folder-a | ||
| // <root>/folder-a/file | ||
| // <root>/symDir -> <root>/folder-a | ||
| const root = path.join(getTestTemp(), 'set-to-false') | ||
| await fs.mkdir(path.join(root, 'dir1'), {recursive: true}) | ||
| await fs.mkdir(path.join(root, 'dir2'), {recursive: true}) | ||
| await fs.writeFile( | ||
| path.join(root, 'dir1', 'testfile1.txt'), | ||
| 'test file content' | ||
| ) | ||
| await fs.writeFile( | ||
| path.join(root, 'dir2', 'testfile2.txt'), | ||
| 'test file content' | ||
| ) | ||
| const testPath = `${path.join(root, 'dir1')}\n${path.join(root, 'dir2')}` | ||
| const hash = await hashFiles(testPath) | ||
| expect(hash).toEqual( | ||
| '4e911ea5824830b6a3ec096c7833d5af8381c189ffaa825c3503a5333a73eadc' | ||
| ) | ||
| }) | ||
| }) | ||
|
|
||
| function getTestTemp(): string { | ||
thboop marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return path.join(__dirname, '_temp', 'hash_files') | ||
| } | ||
|
|
||
| /** | ||
| * Creates a symlink directory on OSX/Linux, and a junction point directory on Windows. | ||
| * A symlink directory is not created on Windows since it requires an elevated context. | ||
| */ | ||
| async function createSymlinkDir(real: string, link: string): Promise<void> { | ||
| if (IS_WINDOWS) { | ||
| await fs.symlink(real, link, 'junction') | ||
| } else { | ||
| await fs.symlink(real, link) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| /** | ||
| * Options to control globbing behavior | ||
| */ | ||
| export interface HashFileOptions { | ||
| /** | ||
| * Indicates whether to follow symbolic links. Generally should set to false | ||
| * when deleting files. | ||
| * | ||
| * @default true | ||
| */ | ||
| followSymbolicLinks?: boolean | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| import * as crypto from 'crypto' | ||
| import * as core from '@actions/core' | ||
| import * as fs from 'fs' | ||
| import * as stream from 'stream' | ||
| import * as util from 'util' | ||
| import * as path from 'path' | ||
| import {Globber} from './glob' | ||
|
|
||
| export async function hashFiles(globber: Globber): Promise<string> { | ||
| let hasMatch = false | ||
| const githubWorkspace = process.env['GITHUB_WORKSPACE'] ?? process.cwd() | ||
| const result = crypto.createHash('sha256') | ||
| let count = 0 | ||
| for await (const file of globber.globGenerator()) { | ||
| core.debug(file) | ||
| if (!file.startsWith(`${githubWorkspace}${path.sep}`)) { | ||
| core.debug(`Ignore '${file}' since it is not under GITHUB_WORKSPACE.`) | ||
| continue | ||
| } | ||
| if (fs.statSync(file).isDirectory()) { | ||
| core.debug(`Skip directory '${file}'.`) | ||
| continue | ||
| } | ||
| const hash = crypto.createHash('sha256') | ||
| const pipeline = util.promisify(stream.pipeline) | ||
| await pipeline(fs.createReadStream(file), hash) | ||
| result.write(hash.digest()) | ||
| count++ | ||
| if (!hasMatch) { | ||
| hasMatch = true | ||
| } | ||
| } | ||
| result.end() | ||
|
|
||
| if (hasMatch) { | ||
| core.debug(`Found ${count} files to hash.`) | ||
| return result.digest('hex') | ||
| } else { | ||
| core.warning(`No matches found for glob`) | ||
| return '' | ||
| } | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.