Skip to content
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

feat(utils): add git helper #469

Merged
merged 43 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
324262a
feat(utils): add git helper
BioPhoton Feb 5, 2024
59a56f4
wip
BioPhoton Feb 5, 2024
2c2f334
wip
BioPhoton Feb 5, 2024
d11b939
test(utils): fix tests
BioPhoton Feb 5, 2024
6455d85
Update packages/utils/src/lib/git.ts
BioPhoton Feb 6, 2024
e7759d0
Update packages/utils/src/lib/git.ts
BioPhoton Feb 6, 2024
8c527a0
refactor(nx-plugin): fix test
BioPhoton Feb 6, 2024
7b156f2
Update packages/utils/src/lib/git.integration.test.ts
BioPhoton Feb 6, 2024
37ea33b
Update packages/utils/src/lib/git.integration.test.ts
BioPhoton Feb 6, 2024
265321c
Update packages/utils/src/lib/git.integration.test.ts
BioPhoton Feb 6, 2024
a335de1
Update packages/utils/src/lib/git.integration.test.ts
BioPhoton Feb 6, 2024
a8282e1
Update packages/utils/src/lib/git.ts
BioPhoton Feb 6, 2024
35df331
refactor(nx-plugin): adopt phrasing
BioPhoton Feb 6, 2024
aa0c400
refactor(nx-plugin): adopt comments
BioPhoton Feb 6, 2024
faeb9cd
Update packages/utils/src/lib/git.integration.test.ts
BioPhoton Feb 6, 2024
8b25354
Update packages/utils/src/lib/git.integration.test.ts
BioPhoton Feb 6, 2024
faf5edd
Update packages/utils/src/lib/git.integration.test.ts
BioPhoton Feb 6, 2024
8070800
refactor(nx-plugin): fix build
BioPhoton Feb 6, 2024
4b7ee9a
refactor(nx-plugin): format
BioPhoton Feb 6, 2024
a6cc8e0
wip
BioPhoton Feb 6, 2024
b1f14e0
wip
BioPhoton Feb 6, 2024
f1baf5a
wip
BioPhoton Feb 6, 2024
f76b4aa
merge main
BioPhoton Feb 15, 2024
9fd5cf5
merge main
BioPhoton Feb 16, 2024
1eb500f
fix merge
BioPhoton Feb 16, 2024
5a14534
fix merge
BioPhoton Feb 16, 2024
9a78bb6
test(utils): add git utils tests
BioPhoton Feb 16, 2024
b940eda
test(utils): add git utils integration tests
BioPhoton Feb 16, 2024
648b73c
refactor(utils): format
BioPhoton Feb 16, 2024
b971a60
merge main
BioPhoton Feb 16, 2024
5292e22
test(utils): add git tests
BioPhoton Feb 17, 2024
1c37d21
fix(utils): make git status clean
BioPhoton Feb 17, 2024
a4d0699
test(utils): add git helper test for empty git repo
BioPhoton Feb 17, 2024
fa39411
fix: lint
BioPhoton Feb 17, 2024
1435214
Update packages/utils/src/lib/git.ts
BioPhoton Feb 19, 2024
b528a75
Update packages/utils/src/lib/git.integration.test.ts
BioPhoton Feb 19, 2024
6d77e3f
refactor(utils): remove isStatusClean function
BioPhoton Feb 20, 2024
e67389a
refactor(utils): adjust beforeEach to set up all branches
BioPhoton Feb 20, 2024
3541510
refactor(utils): adjust error message
BioPhoton Feb 20, 2024
c968d70
Update packages/utils/src/lib/git.ts
BioPhoton Feb 23, 2024
51984cc
test(examples-plugins): refactor hooks
BioPhoton Feb 23, 2024
6e2cd90
fix(examples-plugins): lint
BioPhoton Feb 23, 2024
1a4206d
Merge branch 'main' into add-git-helper
BioPhoton Feb 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 100 additions & 2 deletions packages/utils/src/lib/git.integration.test.ts
BioPhoton marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,20 @@ import { mkdir, rm, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { type SimpleGit, simpleGit } from 'simple-git';
import { expect } from 'vitest';
import { getGitRoot, getLatestCommit, toGitPath } from './git';
import {
getCurrentBranchOrTag,
getGitRoot,
getLatestCommit,
guardAgainstLocalChanges,
safeCheckout,
statusIsClean,
toGitPath,
} from './git';
import { toUnixPath } from './transform';

describe('git utils', () => {
describe('git utils in a git repo with a branch and commits', () => {
const baseDir = join(process.cwd(), 'tmp', 'testing-git-repo');
const changesDir = join(baseDir, 'changes-dir');
let git: SimpleGit;

beforeAll(async () => {
Expand All @@ -21,12 +30,19 @@ describe('git utils', () => {

await git.add('README.md');
await git.commit('Create README');

await git.checkout(['-b', 'feature-branch']);
BioPhoton marked this conversation as resolved.
Show resolved Hide resolved
});

afterAll(async () => {
await rm(baseDir, { recursive: true, force: true });
});

afterEach(async () => {
await rm(changesDir, { recursive: true, force: true });
BioPhoton marked this conversation as resolved.
Show resolved Hide resolved
await git.checkout(['master']);
});
BioPhoton marked this conversation as resolved.
Show resolved Hide resolved

it('should log latest commit', async () => {
const gitCommitDateRegex =
/^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{1,2} \d{2}:\d{2}:\d{2} \d{4} [+|-]\d{4}$/;
Expand Down Expand Up @@ -60,4 +76,86 @@ describe('git utils', () => {
'Backend/API/Startup.cs',
);
});

it('statusIsClean should return false if some changes are given', async () => {
await mkdir(changesDir, { recursive: true });
await writeFile(join(changesDir, 'change.md'), '# hello-change\n');
await expect(statusIsClean(git)).resolves.toBe(false);
});

it('statusIsClean should return true if no changes are given', async () => {
await expect(statusIsClean(git)).resolves.toBe(true);
});
BioPhoton marked this conversation as resolved.
Show resolved Hide resolved

it('guardAgainstLocalChanges should throw if history is dirty', async () => {
await mkdir(changesDir, { recursive: true });
await writeFile(join(changesDir, 'change.md'), '# hello-change\n');
await expect(guardAgainstLocalChanges(git)).rejects.toThrow(
'Working directory needs to be clean before we you can proceed. Commit your local changes or stash them.',
);
});

it('guardAgainstLocalChanges should not throw if history is clean', async () => {
await expect(guardAgainstLocalChanges(git)).resolves.toBeUndefined();
});

it('safeCheckout should checkout target branch in clean state', async () => {
await expect(git.branch()).resolves.toEqual(
expect.objectContaining({ current: 'master' }),
);
await expect(
safeCheckout('feature-branch', {}, git),
).resolves.toBeUndefined();
await expect(git.branch()).resolves.toEqual(
expect.objectContaining({ current: 'feature-branch' }),
);
});

it('safeCheckout should throw if history is dirty', async () => {
await mkdir(changesDir, { recursive: true });
await writeFile(join(changesDir, 'change.md'), '# hello-change\n');
await expect(safeCheckout('master', {}, git)).rejects.toThrow(
'Working directory needs to be clean before we you can proceed. Commit your local changes or stash them.',
);
});

it('safeCheckout should clean local changes and check out to master', async () => {
BioPhoton marked this conversation as resolved.
Show resolved Hide resolved
// needs to get reset to be clean
await mkdir(changesDir, { recursive: true });
await writeFile(join(changesDir, 'change.md'), '# hello-change\n');
// needs to get cleaned to be clean
await writeFile(join(baseDir, 'README.md'), '# hello-world-2\n');
BioPhoton marked this conversation as resolved.
Show resolved Hide resolved

await expect(
safeCheckout('feature-branch', { forceCleanStatus: true }, git),
).resolves.toBeUndefined();
await expect(git.branch()).resolves.toEqual(
expect.objectContaining({ current: 'feature-branch' }),
);
});

it('getCurrentBranchOrTag should log current branch', async () => {
await expect(getCurrentBranchOrTag(git)).resolves.toBe('master');
});
});

describe('git utils in a git repo without a branch and commits', () => {
const baseDir = join(process.cwd(), 'tmp', 'testing-git-repo');
let git: SimpleGit;

beforeAll(async () => {
await mkdir(baseDir, { recursive: true });
git = simpleGit(baseDir);
await git.init();
});

afterAll(async () => {
await rm(baseDir, { recursive: true, force: true });
});

it('getCurrentBranchOrTag should throw if no branch is given', async () => {
await expect(getCurrentBranchOrTag(git)).rejects.toThrow(
"git: 'describe --tags --exact-match' is not a git command. See 'git --help'",
BioPhoton marked this conversation as resolved.
Show resolved Hide resolved
);
});
});
49 changes: 46 additions & 3 deletions packages/utils/src/lib/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,60 @@ export async function toGitPath(

export function validateCommitData(
commitData: CommitData | null,
options: { throwError?: boolean } = {},
options: { throwError?: true } = {},
): commitData is CommitData {
const { throwError = false } = options;
if (!commitData) {
const msg = 'no commit data available';
if (throwError) {
if (options?.throwError) {
throw new Error(msg);
} else {
// @TODO replace with ui().logger.warning
console.warn(msg);
return false;
}
}
return true;
}

export function statusIsClean(git = simpleGit()): Promise<boolean> {
return git.status(['-s']).then(r => r.files.length === 0);
}

export async function guardAgainstLocalChanges(
git = simpleGit(),
): Promise<void> {
const isClean = await statusIsClean(git);
if (!isClean) {
throw new Error(
'Working directory needs to be clean before we you can proceed. Commit your local changes or stash them.',
);
}
}

export async function getCurrentBranchOrTag(
git = simpleGit(),
): Promise<string> {
return (
(await git.branch().then(r => r.current)) ||
// @TODO replace with simple git
(await git.raw(['describe --tags --exact-match']).then(out => out.trim()))
BioPhoton marked this conversation as resolved.
Show resolved Hide resolved
);
}

export async function safeCheckout(
branchOrHash: string,
options: {
forceCleanStatus?: true;
} = {},
git = simpleGit(),
): Promise<void> {
// git requires a clean history to check out a branch
if (options?.forceCleanStatus) {
await git.raw(['reset', '--hard']);
await git.clean(['f', 'd']);
BioPhoton marked this conversation as resolved.
Show resolved Hide resolved
// @TODO replace with ui().logger.info
console.info(`git status cleaned`);
}
await guardAgainstLocalChanges(git);
await git.checkout(branchOrHash);
}
2 changes: 1 addition & 1 deletion tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
],
"@code-pushup/models": ["packages/models/src/index.ts"],
"@code-pushup/nx-plugin": ["packages/nx-plugin/src/index.ts"],
"@code-pushup/test-utils": ["testing/test-utils/src/index.ts"],
"@code-pushup/test-setup": ["testing/test-setup/src/index.ts"],
"@code-pushup/test-utils": ["testing/test-utils/src/index.ts"],
"@code-pushup/utils": ["packages/utils/src/index.ts"]
}
},
Expand Down