Skip to content

Commit

Permalink
feat: support linting from the last tag (#4110)
Browse files Browse the repository at this point in the history
* fix: observe working directory with --last option

* feat: support linting from the last tag
  • Loading branch information
benquarmby committed Aug 4, 2024
1 parent a20e890 commit 4b204ec
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 6 deletions.
1 change: 1 addition & 0 deletions @commitlint/cli/src/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,7 @@ test('should print help', async () => {
-x, --extends array of shareable configurations to extend [array]
-H, --help-url help url in error message [string]
-f, --from lower end of the commit range to lint; applies if edit=false [string]
--from-last-tag uses the last tag as the lower end of the commit range to lint; applies if edit=false and from is not set [boolean]
--git-log-args additional git log arguments as space separated string, example '--first-parent --cherry-pick' [string]
-l, --last just analyze the last commit; applies if edit=false [boolean]
-o, --format output format of the results [string]
Expand Down
7 changes: 7 additions & 0 deletions @commitlint/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ const cli = yargs(process.argv.slice(2))
'lower end of the commit range to lint; applies if edit=false',
type: 'string',
},
'from-last-tag': {
description:
'uses the last tag as the lower end of the commit range to lint; applies if edit=false and from is not set',
type: 'boolean',
},
'git-log-args': {
description:
"additional git log arguments as space separated string, example '--first-parent --cherry-pick'",
Expand Down Expand Up @@ -242,6 +247,7 @@ async function main(args: MainArgs): Promise<void> {
: read({
to: flags.to,
from: flags.from,
fromLastTag: flags['from-last-tag'],
last: flags.last,
edit: flags.edit,
cwd: flags.cwd,
Expand Down Expand Up @@ -400,6 +406,7 @@ function checkFromEdit(flags: CliFlags): boolean {
function checkFromHistory(flags: CliFlags): boolean {
return (
typeof flags.from === 'string' ||
typeof flags['from-last-tag'] === 'boolean' ||
typeof flags.to === 'string' ||
typeof flags.last === 'boolean'
);
Expand Down
1 change: 1 addition & 0 deletions @commitlint/cli/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface CliFlags {
help?: boolean;
'help-url'?: string;
from?: string;
'from-last-tag'?: boolean;
'git-log-args'?: string;
last?: boolean;
format?: string;
Expand Down
58 changes: 58 additions & 0 deletions @commitlint/read/src/read.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,61 @@ test('get edit commit message while skipping first commit', async () => {
const actual = await read({from: 'HEAD~2', cwd, gitLogArgs: '--skip 1'});
expect(actual).toEqual(expected);
});

test('should only read the last commit', async () => {
const cwd: string = await git.bootstrap();

await execa('git', ['commit', '--allow-empty', '-m', 'commit Z'], {cwd});
await execa('git', ['commit', '--allow-empty', '-m', 'commit Y'], {cwd});
await execa('git', ['commit', '--allow-empty', '-m', 'commit X'], {cwd});

const result = await read({cwd, last: true});

expect(result).toEqual(['commit X']);
});

test('should read commits from the last annotated tag', async () => {
const cwd: string = await git.bootstrap();

await execa(
'git',
['commit', '--allow-empty', '-m', 'chore: release v1.0.0'],
{cwd}
);
await execa('git', ['tag', 'v1.0.0', '--annotate', '-m', 'v1.0.0'], {cwd});
await execa('git', ['commit', '--allow-empty', '-m', 'commit 1'], {cwd});
await execa('git', ['commit', '--allow-empty', '-m', 'commit 2'], {cwd});

const result = await read({cwd, fromLastTag: true});

expect(result).toEqual(['commit 2\n\n', 'commit 1\n\n']);
});

test('should read commits from the last lightweight tag', async () => {
const cwd: string = await git.bootstrap();

await execa(
'git',
['commit', '--allow-empty', '-m', 'chore: release v9.9.9-alpha.1'],
{cwd}
);
await execa('git', ['tag', 'v9.9.9-alpha.1'], {cwd});
await execa('git', ['commit', '--allow-empty', '-m', 'commit A'], {cwd});
await execa('git', ['commit', '--allow-empty', '-m', 'commit B'], {cwd});

const result = await read({cwd, fromLastTag: true});

expect(result).toEqual(['commit B\n\n', 'commit A\n\n']);
});

test('should not read any commits when there are no tags', async () => {
const cwd: string = await git.bootstrap();

await execa('git', ['commit', '--allow-empty', '-m', 'commit 7'], {cwd});
await execa('git', ['commit', '--allow-empty', '-m', 'commit 8'], {cwd});
await execa('git', ['commit', '--allow-empty', '-m', 'commit 9'], {cwd});

const result = await read({cwd, fromLastTag: true});

expect(result).toHaveLength(0);
});
42 changes: 36 additions & 6 deletions @commitlint/read/src/read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {execa} from 'execa';
interface GetCommitMessageOptions {
cwd?: string;
from?: string;
fromLastTag?: boolean;
to?: string;
last?: boolean;
edit?: boolean | string;
Expand All @@ -19,25 +20,54 @@ interface GetCommitMessageOptions {
export default async function getCommitMessages(
settings: GetCommitMessageOptions
): Promise<string[]> {
const {cwd, from, to, last, edit, gitLogArgs} = settings;
const {cwd, fromLastTag, to, last, edit, gitLogArgs} = settings;
let from = settings.from;

if (edit) {
return getEditCommit(cwd, edit);
}

if (last) {
const gitCommandResult = await execa('git', [
'log',
'-1',
'--pretty=format:%B',
]);
const gitCommandResult = await execa(
'git',
['log', '-1', '--pretty=format:%B'],
{cwd}
);
let output = gitCommandResult.stdout;
// strip output of extra quotation marks ("")
if (output[0] == '"' && output[output.length - 1] == '"')
output = output.slice(1, -1);
return [output];
}

if (!from && fromLastTag) {
const {stdout} = await execa(
'git',
[
'describe',
'--abbrev=40',
'--always',
'--first-parent',
'--long',
'--tags',
],
{cwd}
);

if (stdout.length === 40) {
// Hash only means no last tag. Use that as the from ref which
// results in a no-op.
from = stdout;
} else {
// Description will be in the format: <tag>-<count>-g<hash>
// Example: v3.2.0-11-g9057371a52adaae5180d93fe4d0bb808d874b9fb
// Minus zero based (1), dash (1), "g" prefix (1), hash (40) = -43
const tagSlice = stdout.lastIndexOf('-', stdout.length - 43);

from = stdout.slice(0, tagSlice);
}
}

let gitOptions: GitOptions = {from, to};
if (gitLogArgs) {
gitOptions = {
Expand Down
2 changes: 2 additions & 0 deletions docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ Options:
-H, --help-url help url in error message [string]
-f, --from lower end of the commit range to lint; applies if
edit=false [string]
--from-last-tag uses the last tag as the lower end of the commit range to
lint; applies if edit=false and from is not set [boolean]
--git-log-args additional git log arguments as space separated string,
example '--first-parent --cherry-pick' [string]
-l, --last just analyze the last commit; applies if edit=false
Expand Down

0 comments on commit 4b204ec

Please sign in to comment.