diff --git a/packages/nx/src/hasher/git-hasher.spec.ts b/packages/nx/src/hasher/git-hasher.spec.ts index 91ea2d663c444..c98af65b2afae 100644 --- a/packages/nx/src/hasher/git-hasher.spec.ts +++ b/packages/nx/src/hasher/git-hasher.spec.ts @@ -1,7 +1,7 @@ -import { dirSync } from 'tmp'; -import { mkdirSync, removeSync } from 'fs-extra'; import { execSync } from 'child_process'; -import { getFileHashes } from './git-hasher'; +import { mkdirSync, removeSync } from 'fs-extra'; +import { dirSync } from 'tmp'; +import { getFileHashes, getGitHashForBatch } from './git-hasher'; describe('git-hasher', () => { let dir: string; @@ -173,4 +173,27 @@ describe('git-hasher', () => { function run(command: string) { return execSync(command, { cwd: dir, stdio: ['pipe', 'pipe', 'pipe'] }); } + + it('should hash two simple files', async () => { + const files = ['a.txt', 'b.txt']; + run(`echo AAA > a.txt`); + run(`echo BBB > b.txt`); + const hashes = await getGitHashForBatch(files, dir); + expect([...hashes.keys()]).toEqual(files); + }); + + it('should fail when file deleted', async () => { + const files = ['a.txt', 'b.txt']; + run(`echo AAA > a.txt`); + try { + const hashes = await getGitHashForBatch(files, dir); + expect(false).toBeTruthy(); + } catch (err: any) { + expect(err instanceof Error).toBeTruthy(); + const error = err as Error; + expect(error.message).toMatch( + /Passed 2 file paths to Git to hash, but received 1 hashes\.\n *fatal:.*b\.txt.*No such file or directory\n/ + ); + } + }); }); diff --git a/packages/nx/src/hasher/git-hasher.ts b/packages/nx/src/hasher/git-hasher.ts index ed5e6e182fe29..e93ce44f15126 100644 --- a/packages/nx/src/hasher/git-hasher.ts +++ b/packages/nx/src/hasher/git-hasher.ts @@ -40,9 +40,9 @@ export async function getGitHashForFiles( return { hashes: res, deleted }; } -async function getGitHashForBatch(filesToHash: string[], path) { +export async function getGitHashForBatch(filesToHash: string[], path) { const res: Map = new Map(); - const hashStdout = await spawnProcess( + const { stdout: hashStdout, stderr: hashStderr } = await spawnProcess( 'git', ['hash-object', ...filesToHash], path @@ -50,7 +50,7 @@ async function getGitHashForBatch(filesToHash: string[], path) { const hashes: string[] = hashStdout.split('\n').filter((s) => !!s); if (hashes.length !== filesToHash.length) { throw new Error( - `Passed ${filesToHash.length} file paths to Git to hash, but received ${hashes.length} hashes.` + `Passed ${filesToHash.length} file paths to Git to hash, but received ${hashes.length} hashes.\n${hashStderr}` ); } for (let i = 0; i < hashes.length; i++) { @@ -67,7 +67,7 @@ function getActualFilesToHash( ): { filesToHash: string[]; deleted: string[] } { const filesToHash = []; const deleted = []; - for (let file of potentialFilesToHash) { + for (const file of potentialFilesToHash) { if (fileExists(joinPathFragments(path, file))) { filesToHash.push(file); } else { @@ -77,28 +77,42 @@ function getActualFilesToHash( return { filesToHash, deleted }; } -async function spawnProcess(command: string, args: string[], cwd: string) { +async function spawnProcess( + command: string, + args: string[], + cwd: string +): Promise<{ code: number; stdout: string; stderr: string }> { const cp = spawn(command, args, { windowsHide: true, shell: false, cwd, }); let stdout = ''; - for await (const data of cp.stdout) { + let stderr = ''; + cp.stdout.on('data', (data) => { stdout += data; - } - return stdout; + }); + cp.stderr.on('data', (data) => { + stderr += data; + }); + return new Promise((resolve) => { + cp.on('close', (code) => { + resolve({ code, stdout, stderr }); + }); + }); } async function getStagedFiles(path: string) { - const staged = await spawnProcess( + const { stdout: staged } = await spawnProcess( 'git', ['ls-files', '-s', '-z', '--exclude-standard', '.'], path ); const res = new Map(); - for (let line of staged.split('\0')) { - if (!line) continue; + for (const line of staged.split('\0')) { + if (!line) { + continue; + } const [_, hash, __, ...fileParts] = line.split(/\s/); const fileName = fileParts.join(' '); res.set(fileName, hash); @@ -107,7 +121,7 @@ async function getStagedFiles(path: string) { } async function getUnstagedFiles(path: string) { - const unstaged = await spawnProcess( + const { stdout: unstaged } = await spawnProcess( 'git', ['ls-files', '-m', '-z', '--exclude-standard', '.'], path @@ -117,7 +131,7 @@ async function getUnstagedFiles(path: string) { } async function getUntrackedFiles(path: string) { - const untracked = await spawnProcess( + const { stdout: untracked } = await spawnProcess( 'git', ['ls-files', '--other', '-z', '--exclude-standard', '.'], path