/
log-parse.ts
170 lines (148 loc) · 4.23 KB
/
log-parse.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import { AsyncSeriesBailHook, AsyncSeriesWaterfallHook } from "tapable";
import { makeLogParseHooks } from "./utils/make-hooks";
export interface ICommitAuthor {
/** Author's name */
name?: string;
/** Author's email */
email?: string;
/** Author's username */
username?: string;
/** The commit this author created */
hash?: string;
/** The type of user */
type?: "Bot" | "User" | string;
}
export interface IPullRequest {
/** The issue number for the pull request */
number: number;
/** The base branch the pull request is on */
base?: string;
/** The body of the PR (opening comment) */
body?: string;
}
export interface ICommit {
/**
*
*/
hash: string;
/**
*
*/
authorName?: string;
/**
*
*/
authorEmail?: string;
/**
*
*/
subject: string;
/**
*
*/
rawBody?: string;
/**
*
*/
labels?: string[];
/**
*
*/
files: string[];
}
export type IExtendedCommit = ICommit & {
/** The authors that contributed to the pull request */
authors: ICommitAuthor[];
/** The pull request information */
pullRequest?: IPullRequest;
/** Labels associated with the commit */
labels: string[];
/** Whether to force include the commit in the changelog */
includeInChangelog?: boolean;
};
/** Parse the PR information for the merge commit message */
export function parsePR(commit: IExtendedCommit): IExtendedCommit {
const merge = /Merge pull request #(\d+) from (.+)\n([\S\s]+)/;
const prMatch = commit.subject.match(merge);
if (!prMatch) {
return commit;
}
return {
...commit,
pullRequest: {
number: Number(prMatch[1]),
base: prMatch[2],
},
subject: prMatch[3].trim(),
};
}
/** Parse the PR information for the squashed commit message */
export function parseSquashPR(commit: IExtendedCommit): IExtendedCommit {
const firstLine = commit.subject.split("\n")[0];
const squashMerge = /\(#(\d+)\)$/;
const squashMergeMatch = firstLine.match(squashMerge);
if (!squashMergeMatch) {
return commit;
}
return {
...commit,
pullRequest: {
number: Number(squashMergeMatch[1]),
},
subject: firstLine
.substr(0, firstLine.length - squashMergeMatch[0].length)
.trim(),
};
}
export interface ILogParseHooks {
/** Parse information about a commit from a commit. Here is where `auto` gets the PR number from the merge commits. */
parseCommit: AsyncSeriesWaterfallHook<[IExtendedCommit]>;
/** Choose to omit certain commits. If you return true the commit will be omitted. Be sure to return nothing if you don't want the commit omitted. */
omitCommit: AsyncSeriesBailHook<[IExtendedCommit], boolean | void>;
}
/**
* Parse the gitlog for commits that are PRs and attach their labels.
* This class can also be tapped into via plugin to parse commits
* in other ways (ex: conventional-commits)
*/
export default class LogParse {
/** Plugin entry points */
hooks: ILogParseHooks;
/** Initialize the log parser and tap the default functionality */
constructor() {
this.hooks = makeLogParseHooks();
this.hooks.parseCommit.tap("Merge Commit", parsePR);
this.hooks.parseCommit.tap("Squash Merge Commit", parseSquashPR);
this.hooks.parseCommit.tap(
"Strip consecutive white-space in Titles",
(commit) => {
const [firstLine, ...lines] = commit.subject.split("\n");
commit.subject = [
firstLine.replace(/[^\S\r\n]{2,}/g, " "),
...lines,
].join("\n");
return commit;
}
);
}
/** Run the log parser over a set of commits */
async normalizeCommits(commits: ICommit[]): Promise<IExtendedCommit[]> {
const eCommits = await Promise.all(
commits.map(async (commit) => this.normalizeCommit(commit))
);
return eCommits.filter(Boolean) as IExtendedCommit[];
}
/** Process a commit to find it's labels and PR information */
async normalizeCommit(commit: ICommit): Promise<IExtendedCommit | undefined> {
const extended = await this.hooks.parseCommit.promise({
labels: [],
...commit,
authors: [{ name: commit.authorName, email: commit.authorEmail }],
});
const shouldOmit = await this.hooks.omitCommit.promise(extended);
if (shouldOmit) {
return;
}
return extended;
}
}