2020-08-12 12:40:37 -04:00
|
|
|
/**
|
|
|
|
* @license
|
|
|
|
* Copyright Google LLC All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
|
|
* found in the LICENSE file at https://angular.io/license
|
|
|
|
*/
|
|
|
|
|
2021-03-19 15:11:21 -04:00
|
|
|
import {Commit as ParsedCommit, Options, sync as parse} from 'conventional-commits-parser';
|
|
|
|
|
2020-11-18 13:57:24 -05:00
|
|
|
import {exec} from '../utils/shelljs';
|
|
|
|
|
2021-03-19 15:11:21 -04:00
|
|
|
|
|
|
|
/** A parsed commit, containing the information needed to validate the commit. */
|
|
|
|
export interface Commit {
|
|
|
|
/** The full raw text of the commit. */
|
|
|
|
fullText: string;
|
|
|
|
/** The header line of the commit, will be used in the changelog entries. */
|
2020-08-12 12:40:37 -04:00
|
|
|
header: string;
|
2021-03-19 15:11:21 -04:00
|
|
|
/** The full body of the commit, not including the footer. */
|
2020-08-12 12:40:37 -04:00
|
|
|
body: string;
|
2021-03-19 15:11:21 -04:00
|
|
|
/** The footer of the commit, containing issue references and note sections. */
|
|
|
|
footer: string;
|
|
|
|
/** A list of the references to other issues made throughout the commit message. */
|
|
|
|
references: ParsedCommit.Reference[];
|
|
|
|
/** The type of the commit message. */
|
2020-08-12 12:40:37 -04:00
|
|
|
type: string;
|
2021-03-19 15:11:21 -04:00
|
|
|
/** The scope of the commit message. */
|
2020-08-12 12:40:37 -04:00
|
|
|
scope: string;
|
2021-03-19 15:11:21 -04:00
|
|
|
/** The npm scope of the commit message. */
|
|
|
|
npmScope: string;
|
|
|
|
/** The subject of the commit message. */
|
2020-08-12 12:40:37 -04:00
|
|
|
subject: string;
|
2021-03-19 15:11:21 -04:00
|
|
|
/** A list of breaking change notes in the commit message. */
|
|
|
|
breakingChanges: ParsedCommit.Note[];
|
|
|
|
/** A list of deprecation notes in the commit message. */
|
|
|
|
deprecations: ParsedCommit.Note[];
|
|
|
|
/** Whether the commit is a fixup commit. */
|
2020-08-12 12:40:37 -04:00
|
|
|
isFixup: boolean;
|
2021-03-19 15:11:21 -04:00
|
|
|
/** Whether the commit is a squash commit. */
|
2020-08-12 12:40:37 -04:00
|
|
|
isSquash: boolean;
|
2021-03-19 15:11:21 -04:00
|
|
|
/** Whether the commit is a revert commit. */
|
2020-08-12 12:40:37 -04:00
|
|
|
isRevert: boolean;
|
|
|
|
}
|
|
|
|
|
2021-03-19 15:11:21 -04:00
|
|
|
/** Markers used to denote the start of a note section in a commit. */
|
|
|
|
enum NoteSections {
|
|
|
|
BREAKING_CHANGE = 'BREAKING CHANGE',
|
|
|
|
DEPRECATED = 'DEPRECATED',
|
|
|
|
}
|
2020-08-12 12:40:37 -04:00
|
|
|
/** Regex determining if a commit is a fixup. */
|
|
|
|
const FIXUP_PREFIX_RE = /^fixup! /i;
|
|
|
|
/** Regex determining if a commit is a squash. */
|
|
|
|
const SQUASH_PREFIX_RE = /^squash! /i;
|
|
|
|
/** Regex determining if a commit is a revert. */
|
|
|
|
const REVERT_PREFIX_RE = /^revert:? /i;
|
2021-03-19 15:11:21 -04:00
|
|
|
/**
|
|
|
|
* Regex pattern for parsing the header line of a commit.
|
|
|
|
*
|
|
|
|
* Several groups are being matched to be used in the parsed commit object, being mapped to the
|
|
|
|
* `headerCorrespondence` object.
|
|
|
|
*
|
|
|
|
* The pattern can be broken down into component parts:
|
|
|
|
* - `(\w+)` - a capturing group discovering the type of the commit.
|
|
|
|
* - `(?:\((?:([^/]+)\/)?([^)]+)\))?` - a pair of capturing groups to capture the scope and,
|
|
|
|
* optionally the npmScope of the commit.
|
|
|
|
* - `(.*)` - a capturing group discovering the subject of the commit.
|
|
|
|
*/
|
|
|
|
const headerPattern = /^(\w+)(?:\((?:([^/]+)\/)?([^)]+)\))?: (.*)$/;
|
|
|
|
/**
|
|
|
|
* The property names used for the values extracted from the header via the `headerPattern` regex.
|
|
|
|
*/
|
|
|
|
const headerCorrespondence = ['type', 'npmScope', 'scope', 'subject'];
|
|
|
|
/**
|
|
|
|
* Configuration options for the commit parser.
|
|
|
|
*
|
|
|
|
* NOTE: An extended type from `Options` must be used because the current
|
|
|
|
* @types/conventional-commits-parser version does not include the `notesPattern` field.
|
|
|
|
*/
|
|
|
|
const parseOptions: Options&{notesPattern: (keywords: string) => RegExp} = {
|
|
|
|
commentChar: '#',
|
|
|
|
headerPattern,
|
|
|
|
headerCorrespondence,
|
|
|
|
noteKeywords: [NoteSections.BREAKING_CHANGE, NoteSections.DEPRECATED],
|
|
|
|
notesPattern: (keywords: string) => new RegExp(`(${keywords})(?:: ?)(.*)`),
|
|
|
|
};
|
2020-08-12 12:40:37 -04:00
|
|
|
|
2020-08-20 12:17:06 -04:00
|
|
|
|
2021-03-19 15:11:21 -04:00
|
|
|
/** Parse a full commit message into its composite parts. */
|
|
|
|
export function parseCommitMessage(fullText: string): Commit {
|
|
|
|
/** The commit message text with the fixup and squash markers stripped out. */
|
|
|
|
const strippedCommitMsg = fullText.replace(FIXUP_PREFIX_RE, '')
|
|
|
|
.replace(SQUASH_PREFIX_RE, '')
|
|
|
|
.replace(REVERT_PREFIX_RE, '');
|
|
|
|
/** The initially parsed commit. */
|
|
|
|
const commit = parse(strippedCommitMsg, parseOptions);
|
|
|
|
/** A list of breaking change notes from the commit. */
|
|
|
|
const breakingChanges: ParsedCommit.Note[] = [];
|
|
|
|
/** A list of deprecation notes from the commit. */
|
|
|
|
const deprecations: ParsedCommit.Note[] = [];
|
2020-08-12 12:40:37 -04:00
|
|
|
|
2021-03-19 15:11:21 -04:00
|
|
|
// Extract the commit message notes by marked types into their respective lists.
|
|
|
|
commit.notes.forEach((note: ParsedCommit.Note) => {
|
|
|
|
if (note.title === NoteSections.BREAKING_CHANGE) {
|
|
|
|
return breakingChanges.push(note);
|
|
|
|
}
|
|
|
|
if (note.title === NoteSections.DEPRECATED) {
|
|
|
|
return deprecations.push(note);
|
|
|
|
}
|
|
|
|
});
|
2020-08-12 12:40:37 -04:00
|
|
|
|
|
|
|
return {
|
2021-03-19 15:11:21 -04:00
|
|
|
fullText,
|
|
|
|
breakingChanges,
|
|
|
|
deprecations,
|
|
|
|
body: commit.body || '',
|
|
|
|
footer: commit.footer || '',
|
|
|
|
header: commit.header || '',
|
|
|
|
references: commit.references,
|
|
|
|
scope: commit.scope || '',
|
|
|
|
subject: commit.subject || '',
|
|
|
|
type: commit.type || '',
|
|
|
|
npmScope: commit.npmScope || '',
|
|
|
|
isFixup: FIXUP_PREFIX_RE.test(fullText),
|
|
|
|
isSquash: SQUASH_PREFIX_RE.test(fullText),
|
|
|
|
isRevert: REVERT_PREFIX_RE.test(fullText),
|
2020-08-12 12:40:37 -04:00
|
|
|
};
|
|
|
|
}
|
2020-11-18 13:57:24 -05:00
|
|
|
|
|
|
|
/** Retrieve and parse each commit message in a provide range. */
|
2021-03-19 15:11:21 -04:00
|
|
|
export function parseCommitMessagesForRange(range: string): Commit[] {
|
2020-11-18 13:57:24 -05:00
|
|
|
/** A random number used as a split point in the git log result. */
|
|
|
|
const randomValueSeparator = `${Math.random()}`;
|
|
|
|
/**
|
|
|
|
* Custom git log format that provides the commit header and body, separated as expected with the
|
|
|
|
* custom separator as the trailing value.
|
|
|
|
*/
|
|
|
|
const gitLogFormat = `%s%n%n%b${randomValueSeparator}`;
|
|
|
|
|
|
|
|
// Retrieve the commits in the provided range.
|
|
|
|
const result = exec(`git log --reverse --format=${gitLogFormat} ${range}`);
|
|
|
|
if (result.code) {
|
|
|
|
throw new Error(`Failed to get all commits in the range:\n ${result.stderr}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
// Separate the commits from a single string into individual commits.
|
|
|
|
.split(randomValueSeparator)
|
|
|
|
// Remove extra space before and after each commit message.
|
|
|
|
.map(l => l.trim())
|
|
|
|
// Remove any superfluous lines which remain from the split.
|
|
|
|
.filter(line => !!line)
|
|
|
|
// Parse each commit message.
|
|
|
|
.map(commit => parseCommitMessage(commit));
|
|
|
|
}
|