cli-node: refactor git into GitUtils

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-04-06 11:38:56 +02:00
parent 43ccc06c5d
commit 9b3b4e6aef
6 changed files with 64 additions and 56 deletions
@@ -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),
);
});
});
@@ -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;
}
+1 -1
View File
@@ -14,4 +14,4 @@
* limitations under the License.
*/
export { listChangedFiles, readFileAtRef } from './git';
export { GitUtils } from './GitUtils';
+2
View File
@@ -19,3 +19,5 @@
*
* @packageDocumentation
*/
export * from './git';
@@ -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: {
@@ -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<string, PackageGraphNode> {
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<string, PackageGraphNode> {
paths.resolveTargetRoot('yarn.lock'),
);
otherLockfile = Lockfile.parse(
await readFileAtRef('yarn.lock', options.ref),
await GitUtils.readFileAtRef('yarn.lock', options.ref),
);
} catch (error) {
console.warn(