107 lines
3.5 KiB
TypeScript
107 lines
3.5 KiB
TypeScript
/**
|
|
* @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
|
|
*/
|
|
|
|
import {exec} from '../utils/shelljs';
|
|
|
|
/** A parsed commit message. */
|
|
export interface ParsedCommitMessage {
|
|
header: string;
|
|
body: string;
|
|
bodyWithoutLinking: string;
|
|
type: string;
|
|
scope: string;
|
|
subject: string;
|
|
isFixup: boolean;
|
|
isSquash: boolean;
|
|
isRevert: boolean;
|
|
}
|
|
|
|
/** Regex determining if a commit is a fixup. */
|
|
const FIXUP_PREFIX_RE = /^fixup! /i;
|
|
/** Regex finding all github keyword links. */
|
|
const GITHUB_LINKING_RE = /((closed?s?)|(fix(es)?(ed)?)|(resolved?s?))\s\#(\d+)/ig;
|
|
/** 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;
|
|
/** Regex determining the scope of a commit if provided. */
|
|
const TYPE_SCOPE_RE = /^(\w+)(?:\(([^)]+)\))?\:\s(.+)$/;
|
|
/** Regex determining the entire header line of the commit. */
|
|
const COMMIT_HEADER_RE = /^(.*)/i;
|
|
/** Regex determining the body of the commit. */
|
|
const COMMIT_BODY_RE = /^.*\n\n([\s\S]*)$/;
|
|
|
|
/** Parse a full commit message into its composite parts. */
|
|
export function parseCommitMessage(commitMsg: string): ParsedCommitMessage {
|
|
// Ignore comments (i.e. lines starting with `#`). Comments are automatically removed by git and
|
|
// should not be considered part of the final commit message.
|
|
commitMsg = commitMsg.split('\n').filter(line => !line.startsWith('#')).join('\n');
|
|
|
|
let header = '';
|
|
let body = '';
|
|
let bodyWithoutLinking = '';
|
|
let type = '';
|
|
let scope = '';
|
|
let subject = '';
|
|
|
|
if (COMMIT_HEADER_RE.test(commitMsg)) {
|
|
header = COMMIT_HEADER_RE.exec(commitMsg)![1]
|
|
.replace(FIXUP_PREFIX_RE, '')
|
|
.replace(SQUASH_PREFIX_RE, '');
|
|
}
|
|
if (COMMIT_BODY_RE.test(commitMsg)) {
|
|
body = COMMIT_BODY_RE.exec(commitMsg)![1];
|
|
bodyWithoutLinking = body.replace(GITHUB_LINKING_RE, '');
|
|
}
|
|
|
|
if (TYPE_SCOPE_RE.test(header)) {
|
|
const parsedCommitHeader = TYPE_SCOPE_RE.exec(header)!;
|
|
type = parsedCommitHeader[1];
|
|
scope = parsedCommitHeader[2];
|
|
subject = parsedCommitHeader[3];
|
|
}
|
|
return {
|
|
header,
|
|
body,
|
|
bodyWithoutLinking,
|
|
type,
|
|
scope,
|
|
subject,
|
|
isFixup: FIXUP_PREFIX_RE.test(commitMsg),
|
|
isSquash: SQUASH_PREFIX_RE.test(commitMsg),
|
|
isRevert: REVERT_PREFIX_RE.test(commitMsg),
|
|
};
|
|
}
|
|
|
|
/** Retrieve and parse each commit message in a provide range. */
|
|
export function parseCommitMessagesForRange(range: string): ParsedCommitMessage[] {
|
|
/** 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));
|
|
}
|