When attempting to actually rely on `parseCommitMessagesForRange`, it became apparent that the function really belongs in the parse file, rather than utils. PR Close #39747
		
			
				
	
	
		
			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));
 | 
						|
}
 |