diff --git a/packages/cli-node/src/git/git.test.ts b/packages/cli-node/src/git/GitUtils.test.ts similarity index 85% rename from packages/cli-node/src/git/git.test.ts rename to packages/cli-node/src/git/GitUtils.test.ts index 2e5d464796..e1ebe5513c 100644 --- a/packages/cli-node/src/git/git.test.ts +++ b/packages/cli-node/src/git/GitUtils.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { runGit, listChangedFiles } from './git'; +import { runGit, GitUtils } from './GitUtils'; describe('runGit', () => { it('runs a git command', async () => { @@ -43,10 +43,14 @@ describe('runGit', () => { describe('listChangedFiles', () => { it('requires a ref', async () => { - await expect(listChangedFiles('')).rejects.toThrow('ref is required'); + await expect(GitUtils.listChangedFiles('')).rejects.toThrow( + 'ref is required', + ); }); it('should return something', async () => { - await expect(listChangedFiles('HEAD')).resolves.toEqual(expect.any(Array)); + await expect(GitUtils.listChangedFiles('HEAD')).resolves.toEqual( + expect.any(Array), + ); }); }); diff --git a/packages/cli-node/src/git/git.ts b/packages/cli-node/src/git/GitUtils.ts similarity index 50% rename from packages/cli-node/src/git/git.ts rename to packages/cli-node/src/git/GitUtils.ts index 74127eae38..df04b1c6a2 100644 --- a/packages/cli-node/src/git/git.ts +++ b/packages/cli-node/src/git/GitUtils.ts @@ -15,11 +15,8 @@ */ import { assertError, ForwardedError } from '@backstage/errors'; -import { execFile as execFileCb } from 'child_process'; -import { promisify } from 'util'; import { paths } from '../paths'; - -const execFile = promisify(execFileCb); +import { execFile } from '../run'; /** * Run a git command, trimming the output splitting it into lines. @@ -43,44 +40,55 @@ export async function runGit(...args: string[]) { } /** - * Returns a sorted list of all files that have changed since the merge base - * of the provided `ref` and HEAD, as well as all files that are not tracked by git. + * Utilities for working with git. + * + * @public */ -export async function listChangedFiles(ref: string) { - if (!ref) { - throw new Error('ref is required'); +export class GitUtils { + /** + * Returns a sorted list of all files that have changed since the merge base + * of the provided `ref` and HEAD, as well as all files that are not tracked by git. + */ + static async listChangedFiles(ref: string) { + if (!ref) { + throw new Error('ref is required'); + } + + let diffRef = ref; + try { + const [base] = await runGit('merge-base', 'HEAD', ref); + diffRef = base; + } catch { + // silently fall back to using the ref directly if merge base is not available + } + + const tracked = await runGit('diff', '--name-only', diffRef); + const untracked = await runGit( + 'ls-files', + '--others', + '--exclude-standard', + ); + + return Array.from(new Set([...tracked, ...untracked])); } - let diffRef = ref; - try { - const [base] = await runGit('merge-base', 'HEAD', ref); - diffRef = base; - } catch { - // silently fall back to using the ref directly if merge base is not available + /** + * Returns the contents of a file at a specific ref. + */ + static async readFileAtRef(path: string, ref: string) { + let showRef = ref; + try { + const [base] = await runGit('merge-base', 'HEAD', ref); + showRef = base; + } catch { + // silently fall back to using the ref directly if merge base is not available + } + + const { stdout } = await execFile('git', ['show', `${showRef}:${path}`], { + shell: true, + cwd: paths.targetRoot, + maxBuffer: 1024 * 1024 * 50, + }); + return stdout; } - - const tracked = await runGit('diff', '--name-only', diffRef); - const untracked = await runGit('ls-files', '--others', '--exclude-standard'); - - return Array.from(new Set([...tracked, ...untracked])); -} - -/** - * Returns the contents of a file at a specific ref. - */ -export async function readFileAtRef(path: string, ref: string) { - let showRef = ref; - try { - const [base] = await runGit('merge-base', 'HEAD', ref); - showRef = base; - } catch { - // silently fall back to using the ref directly if merge base is not available - } - - const { stdout } = await execFile('git', ['show', `${showRef}:${path}`], { - shell: true, - cwd: paths.targetRoot, - maxBuffer: 1024 * 1024 * 50, - }); - return stdout; } diff --git a/packages/cli-node/src/git/index.ts b/packages/cli-node/src/git/index.ts index d962f57258..b4dd156ed2 100644 --- a/packages/cli-node/src/git/index.ts +++ b/packages/cli-node/src/git/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { listChangedFiles, readFileAtRef } from './git'; +export { GitUtils } from './GitUtils'; diff --git a/packages/cli-node/src/index.ts b/packages/cli-node/src/index.ts index fcd851e2ed..51913e9b9e 100644 --- a/packages/cli-node/src/index.ts +++ b/packages/cli-node/src/index.ts @@ -19,3 +19,5 @@ * * @packageDocumentation */ + +export * from './git'; diff --git a/packages/cli-node/src/monorepo/PackageGraph.test.ts b/packages/cli-node/src/monorepo/PackageGraph.test.ts index 8f34999f93..9bc9cbf72b 100644 --- a/packages/cli-node/src/monorepo/PackageGraph.test.ts +++ b/packages/cli-node/src/monorepo/PackageGraph.test.ts @@ -18,16 +18,10 @@ import { resolve as resolvePath } from 'path'; import { getPackages } from '@manypkg/get-packages'; import { PackageGraph } from './PackageGraph'; import { Lockfile } from './Lockfile'; -import { listChangedFiles, readFileAtRef } from '../git'; +import { GitUtils } from '../git'; -jest.mock('../git'); - -const mockListChangedFiles = listChangedFiles as jest.MockedFunction< - typeof listChangedFiles ->; -const mockReadFileAtRef = readFileAtRef as jest.MockedFunction< - typeof readFileAtRef ->; +const mockListChangedFiles = jest.spyOn(GitUtils, 'listChangedFiles'); +const mockReadFileAtRef = jest.spyOn(GitUtils, 'readFileAtRef'); jest.mock('../paths', () => ({ paths: { diff --git a/packages/cli-node/src/monorepo/PackageGraph.ts b/packages/cli-node/src/monorepo/PackageGraph.ts index 704b35d531..2ab0edb68a 100644 --- a/packages/cli-node/src/monorepo/PackageGraph.ts +++ b/packages/cli-node/src/monorepo/PackageGraph.ts @@ -18,7 +18,7 @@ import path from 'path'; import { getPackages, Package } from '@manypkg/get-packages'; import { paths } from '../paths'; import { PackageRole } from '../role'; -import { listChangedFiles, readFileAtRef } from '../git'; +import { GitUtils } from '../git'; import { Lockfile } from './Lockfile'; import { JsonValue } from '@backstage/types'; @@ -214,7 +214,7 @@ export class PackageGraph extends Map { ref: string; analyzeLockfile?: boolean; }) { - const changedFiles = await listChangedFiles(options.ref); + const changedFiles = await GitUtils.listChangedFiles(options.ref); const dirMap = new Map( Array.from(this.values()).map(pkg => [ @@ -265,7 +265,7 @@ export class PackageGraph extends Map { paths.resolveTargetRoot('yarn.lock'), ); otherLockfile = Lockfile.parse( - await readFileAtRef('yarn.lock', options.ref), + await GitUtils.readFileAtRef('yarn.lock', options.ref), ); } catch (error) { console.warn(