-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Expand file tree
/
Copy pathcherry-picks-section.ts
More file actions
105 lines (87 loc) · 3.1 KB
/
cherry-picks-section.ts
File metadata and controls
105 lines (87 loc) · 3.1 KB
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
/**
* Extracts commits from release branch and builds collapsible markdown section.
*/
import { execFileSync } from 'child_process';
const REPO_URL = process.env.GITHUB_REPOSITORY
? `https://github.com/${process.env.GITHUB_REPOSITORY}`
: 'https://github.com/MetaMask/metamask-mobile';
const SKIP_CI_BUMP_VERSION_SUBJECT = /^\[skip ci\] Bump version number to/;
function getMergeBase(headRef: string, baseRef: string): string {
const out = execFileSync('git', ['merge-base', headRef, baseRef], {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
});
const sha = out.trim();
if (!sha) {
throw new Error(`git merge-base returned empty for ${headRef} and ${baseRef}`);
}
return sha;
}
function getAncestryPathCommits(
mergeBaseHash: string,
headRef: string,
): { hash: string; subject: string }[] {
const out = execFileSync(
'git',
['log', '--ancestry-path', `${mergeBaseHash}..${headRef}`, '--pretty=format:%h\t%s'],
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] },
);
if (!out.trim()) return [];
return out
.trim()
.split('\n')
.filter((line) => {
const tab = line.indexOf('\t');
const subject = tab >= 0 ? line.slice(tab + 1) : line;
return !SKIP_CI_BUMP_VERSION_SUBJECT.test(subject.trim());
})
.map((line) => {
const tab = line.indexOf('\t');
return {
hash: tab >= 0 ? line.slice(0, tab) : '',
subject: tab >= 0 ? line.slice(tab + 1) : line,
};
});
}
function formatSubjectWithPrLinks(subject: string): string {
return subject
.replace(/\(#(\d+)\)/g, (_, n) => `([#${n}](${REPO_URL}/pull/${n}))`)
.replace(/(^|\s)#(\d+)\b/g, (_, lead, n) => `${lead}[#${n}](${REPO_URL}/pull/${n})`);
}
export function extractCherryPicks(): { hash: string; subject: string }[] {
const baseRef = process.env.MERGE_BASE_REF ?? 'origin/main';
const headRef = (process.env.HEAD_REF ?? '').trim() || 'HEAD';
const mergeBase = getMergeBase(headRef, baseRef);
const commits = getAncestryPathCommits(mergeBase, headRef);
console.log(`[cherry-picks] Found ${commits.length} commit(s)`);
return commits;
}
export function buildCherryPicksSection(
commits: { hash: string; subject: string }[],
): string {
if (commits.length === 0) return '';
const lines: string[] = [
'<a id="cherry-picks"></a>',
'### :cherries: What\'s in this RC\n',
'<details>',
`<summary>${commits.length} commit(s) in this release</summary>\n`,
'| Commit | Description |',
'| :--- | :--- |',
];
for (const commit of commits) {
const linkedSubject = formatSubjectWithPrLinks(commit.subject);
const commitLink = `[\`${commit.hash}\`](${REPO_URL}/commit/${commit.hash})`;
lines.push(`| ${commitLink} | ${linkedSubject} |`);
}
lines.push('\n</details>\n');
return lines.join('\n');
}
export function buildCherryPicksFailureSection(error?: string): string {
let section = `<a id="cherry-picks"></a>
### :cherries: What's in this RC
_Could not extract commit list._`;
if (error) {
section += `\n\n<details>\n<summary>Details</summary>\n\n${error}\n\n</details>`;
}
return section + '\n';
}