Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions scripts/build-release-changelog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,17 +431,22 @@ async function generateGitHubNotes(
}

export function parseGitLog(raw: string): Array<Omit<ReleaseCommit, "pulls">> {
const fields = raw.split("\0");
if (fields.at(-1) === "") fields.pop();
if (fields.length % 3 !== 0) {
throw new Error("git log produced a malformed release commit record");
}

const commits: Array<Omit<ReleaseCommit, "pulls">> = [];
for (const record of raw.split("\x1e")) {
if (!record.trim()) continue;
const [sha, subject, ...bodyParts] = record.replace(/^\n+/, "").split("\x1f");
if (!sha?.trim() || !subject?.trim()) {
for (let index = 0; index < fields.length; index += 3) {
const [sha, subject, body] = fields.slice(index, index + 3);
if (!sha?.trim() || !subject?.trim() || body === undefined) {
throw new Error("git log produced a malformed release commit record");
}
commits.push({
sha: sha.trim(),
subject: subject.trim(),
body: bodyParts.join("\x1f").trim(),
body: body.trim(),
});
}
return commits;
Expand All @@ -464,7 +469,8 @@ async function releaseCommits(
"log",
"--first-parent",
"--reverse",
"--format=%H%x1f%s%x1f%B%x1e",
"-z",
"--format=%H%x00%s%x00%B",
range,
]);
return parseGitLog(raw);
Expand Down
24 changes: 19 additions & 5 deletions tests/build-release-changelog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,10 @@ describe("commit helpers", () => {
describe("release metadata parsers", () => {
test("parses multiline git-log records and trailing separators", () => {
const raw = [
`${sha("a")}\x1ffix(core): first change\x1ffix(core): first change\n\nline one\nline two\x1e`,
`${sha("b")}\x1ffeat(api): second change\x1ffeat(api): second change\x1e`,
sha("a"), "fix(core): first change", "fix(core): first change\n\nline one\nline two",
sha("b"), "feat(api): second change", "feat(api): second change",
"",
].join("\n");
].join("\0");

expect(parseGitLog(raw)).toEqual([
{
Expand All @@ -156,12 +156,26 @@ describe("release metadata parsers", () => {
});

test("fails closed on malformed git-log records", () => {
expect(() => parseGitLog(`\x1ffix(core): missing sha\x1fbody\x1e`)).toThrow(
expect(() => parseGitLog(`\0fix(core): missing sha\0body\0`)).toThrow(
"malformed release commit record",
);
expect(() => parseGitLog(`${sha("a")}\x1f\x1fbody\x1e`)).toThrow(
expect(() => parseGitLog(`${sha("a")}\0\0body\0`)).toThrow(
"malformed release commit record",
);
expect(() => parseGitLog(`${sha("a")}\0fix(core): missing body\0`)).toThrow(
"malformed release commit record",
);
});

test("preserves control bytes in commit subjects and bodies", () => {
const subject = "release: v1.2.3\x1ffix: visible change";
const body = `${subject}\n\nrecord separator: \x1e`;

expect(parseGitLog(`${sha("a")}\0${subject}\0${body}\0`)).toEqual([{
sha: sha("a"),
subject,
body,
}]);
});

test("normalizes associated pull metadata safely", () => {
Expand Down
Loading