Skip to content

Commit

Permalink
feat: add the git-hook:install command
Browse files Browse the repository at this point in the history
Summary:

Create the `git-hook:install` command to install git hooks. This is the same as
the last one. We have added some extra environment variables to the hooks to
allow for future expansion. This will mainly give us the ability to prompt the
user to update the hooks if they are out of date.

Test Plan:

Tests have been written, I have also tested this manually by running the
command in a sapling and git repo.

Ref: #76
  • Loading branch information
AdeAttwood authored and GeorgeCadwallader committed May 27, 2024
1 parent 13d9354 commit c05840a
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 0 deletions.
45 changes: 45 additions & 0 deletions src/commands/git-hook:install.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import fs from 'node:fs';

import {handlerWrapper} from '../lib/handler-wrapper';
import git from '../lib/source-control/git';

const HOOKS_VERSION = '1';

const buildHook = ({hook}: {hook: string}) => `#!/bin/sh
#
# This hook was installed by conventional-tools
#
export CONVENTIONAL_TOOLS="true"
export CONVENTIONAL_TOOLS_HOOKS_VERSION="${HOOKS_VERSION}"
conventional-tools git-hook ${hook} "$@";
exit $?;
`;

// prettier-ignore
const HOOKS = [
'applypatch-msg', 'commit-msg', 'post-update', 'pre-applypatch',
'pre-commit', 'pre-push', 'pre-rebase', 'pre-receive',
'prepare-commit-msg', 'update',
];

export const builder = {} as const;

export async function handler(): Promise<number> {
if (!(await git.isEnabled())) {
throw new Error(
'Git is not enabled, you must be using git to install hooks',
);
}

for (const hook of HOOKS) {
fs.writeFileSync(`.git/hooks/${hook}`, buildHook({hook}), {
mode: '775',
});
}

return 0;
}

export default {builder, handler: handlerWrapper(handler)};
7 changes: 7 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import yargs from 'yargs';
import {hideBin} from 'yargs/helpers';

import commitgen from './commands/commitgen';
import gitHookInstall from './commands/git-hook:install';

export async function run(args: string[]) {
await yargs(hideBin(args))
Expand All @@ -11,6 +12,12 @@ export async function run(args: string[]) {
commitgen.builder,
commitgen.handler,
)
.command(
'git-hook:install',
'Install git hooks',
gitHookInstall.builder,
gitHookInstall.handler,
)
.strictOptions()
.strict()
.parse();
Expand Down
66 changes: 66 additions & 0 deletions tests/commands/git-hook:install.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import fs from 'node:fs';

import {beforeEach, describe, expect, it, vi} from 'vitest';

import {handler} from '../../src/commands/git-hook:install';
import git from '../../src/lib/source-control/git';

declare module 'vitest' {
export interface TestContext {
commandResult?: number;
commandError?: unknown;
}
}

describe('command/git-hook:install', () => {
describe('with git disabled', () => {
beforeEach(async ctx => {
vi.spyOn(git, 'isEnabled').mockReturnValue(Promise.resolve(false));

try {
ctx.commandResult = await handler();
} catch (error) {
ctx.commandError = error;
}
});

it('does not set the exit code because an error was thrown', ctx => {
expect(ctx.commandResult).toBeUndefined();
});

it('throws and error because only git hooks can be installed with the git hooks command', ctx => {
expect(ctx.commandError).toHaveProperty(
'message',
expect.stringMatching(/git is not enabled/i),
);
});
});

describe('with git enabled', () => {
beforeEach(async ctx => {
vi.spyOn(git, 'isEnabled').mockReturnValue(Promise.resolve(true));
vi.spyOn(fs, 'writeFileSync').mockImplementation(() => {});

ctx.commandResult = await handler();
});

// prettier-ignore
const HOOKS = [
'applypatch-msg', 'commit-msg', 'post-update', 'pre-applypatch',
'pre-commit', 'pre-push', 'pre-rebase', 'pre-receive',
'prepare-commit-msg', 'update',
];

it.each(HOOKS)('writes the %s hook to the file system', hook => {
expect(fs.writeFileSync).toHaveBeenCalledWith(
`.git/hooks/${hook}`,
expect.stringContaining('conventional-tools git-hook'),
{mode: '775'},
);
});

it('only writes the number of files that there are hooks', () => {
expect(fs.writeFileSync).toHaveBeenCalledTimes(HOOKS.length);
});
});
});

0 comments on commit c05840a

Please sign in to comment.