Skip to content

Support linting from the last tag #4110

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

Merged
merged 2 commits into from
Aug 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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']);
});
Comment on lines +76 to +86
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Locked in the --last fix with a test. Previously it was not covered.


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}
);
Comment on lines +31 to +35
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Find a bug, fix a bug. Previously using the --last option didn't observe --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',
],
Comment on lines +45 to +53
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These git arguments have been battle tested against dozens of repos over several years. Most git output is designed for humans, not for machines (i.e. deserialization), so it takes some work to get stable, deterministic output.

{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);
Comment on lines +62 to +65
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably a few ways to skin this cat. The complexity comes from the fact that tags can themselves contain the dash - character (i.e. v1.2.3-alpha.0), and git doesn't escape anything. Need to work backwards from the last dash we can be sure was produced by git.


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