Skip to content
Merged
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
17 changes: 12 additions & 5 deletions .github/local-actions/branch-manager/main.js

Large diffs are not rendered by default.

17 changes: 12 additions & 5 deletions github-actions/labeling/pull-request/main.js

Large diffs are not rendered by default.

42 changes: 26 additions & 16 deletions github-actions/release/publish/main.js

Large diffs are not rendered by default.

39 changes: 38 additions & 1 deletion ng-dev/commit-message/parse.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {parseCommitMessage} from './parse.js';
import {parseCommitFromGitLog, parseCommitMessage} from './parse.js';
import {commitMessageBuilder, CommitMessageParts} from './test-util.js';

const commitValues: CommitMessageParts = {
Expand Down Expand Up @@ -183,4 +183,41 @@ describe('commit message parsing:', () => {
expect(parsedMessage.deprecations.length).toBe(0);
});
});

describe('parses git log metadata', () => {
it('extracts trailing hash, shortHash, and author fields', () => {
const rawLog =
buildCommitMessage() +
'\n-hash-\n0123456789abcdef0123456789abcdef01234567' +
'\n-shortHash-\n0123456' +
'\n-author-\nReal Author';
const parsed = parseCommitFromGitLog(rawLog);
expect(parsed.hash).toBe('0123456789abcdef0123456789abcdef01234567');
expect(parsed.shortHash).toBe('0123456');
expect(parsed.author).toBe('Real Author');
expect(parsed.body).toBe(commitValues.body);
expect(parsed.footer).toBe(commitValues.footer);
});

it('prevents commit body metadata smuggling and scissor truncation', () => {
const smuggledBody =
'Legitimate body text\n' +
'-hash-\n' +
'spoofed-hash\n' +
'-shortHash-\n' +
'spoofed](https://evil.example) [\n' +
'-author-\n' +
'Spoofed Author\n' +
'# ------------------------ >8 ------------------------';
const rawLog =
buildCommitMessage({body: smuggledBody, footer: ''}) +
'\n-hash-\n0123456789abcdef0123456789abcdef01234567' +
'\n-shortHash-\n0123456' +
'\n-author-\nReal Author';
const parsed = parseCommitFromGitLog(rawLog);
expect(parsed.hash).toBe('0123456789abcdef0123456789abcdef01234567');
expect(parsed.shortHash).toBe('0123456');
expect(parsed.author).toBe('Real Author');
});
});
});
21 changes: 17 additions & 4 deletions ng-dev/commit-message/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,18 @@ const headerPattern = /^(\w+)(?:\(([^)]+)\))?: (.*)$/;
* `headerPattern` regex.
*/
const headerCorrespondence = ['type', 'scope', 'subject'];
/**
* Regex matching the trailing git log metadata fields appended by `gitLogFormatForParsing`.
*/
const GIT_LOG_METADATA_RE =
/\r?\n-hash-\r?\n([^\r\n]*)\r?\n-shortHash-\r?\n([^\r\n]*)\r?\n-author-\r?\n([^\r\n]*)\s*$/;

/**
* Configuration options for the commit parser.
*/
const parseOptions: ParserOptions = {
commentChar: '#',
fieldPattern: /(?!)/,
headerPattern,
headerCorrespondence,
noteKeywords: [NoteSections.BREAKING_CHANGE, NoteSections.DEPRECATED],
Expand All @@ -129,8 +136,14 @@ export function parseCommitFromGitLog(fullText: string): CommitFromGitLog {

/** Parse a full commit message into its composite parts. */
function parseInternal(fullText: string): CommitFromGitLog | Commit {
const metadataMatch = fullText.match(GIT_LOG_METADATA_RE);
const messageText = metadataMatch ? fullText.slice(0, metadataMatch.index) : fullText;
const hash = metadataMatch?.[1]?.trim() || undefined;
const shortHash = metadataMatch?.[2]?.trim() || undefined;
const author = metadataMatch?.[3]?.trim() || undefined;

/** The initially parsed commit. */
const commit = commitParser.parse(fullText);
const commit = commitParser.parse(messageText);
/** A list of breaking change notes from the commit. */
const breakingChanges: CommitNote[] = [];
/** A list of deprecation notes from the commit. */
Expand Down Expand Up @@ -168,8 +181,8 @@ function parseInternal(fullText: string): CommitFromGitLog | Commit {
isFixup: FIXUP_PREFIX_RE.test(fullText),
isSquash: SQUASH_PREFIX_RE.test(fullText),
isRevert: REVERT_PREFIX_RE.test(fullText),
author: commit['author'] || undefined,
hash: commit['hash'] || undefined,
shortHash: commit['shortHash'] || undefined,
author,
hash,
shortHash,
};
}
8 changes: 2 additions & 6 deletions ng-dev/release/notes/commits/get-commits-in-range.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,8 @@ function fetchCommitsForRevisionRange(
client: GitClient,
revisionRange: string,
): CommitFromGitLog[] {
const splitDelimiter = '-------------ɵɵ------------';
const output = client.run([
'log',
`--format=${gitLogFormatForParsing}${splitDelimiter}`,
revisionRange,
]);
const splitDelimiter = '\0';
const output = client.run(['log', `--format=${gitLogFormatForParsing}%x00`, revisionRange]);
/** A set of the commits in the provided range. */
const commits = new Map<string, CommitFromGitLog>();

Expand Down
19 changes: 14 additions & 5 deletions ng-dev/release/notes/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,10 @@ export class RenderContext {
* Convert a commit object to a Markdown link.
*/
commitToLink = (commit: CategorizedCommit): string => {
const url = `https://github.com/${this.data.github.owner}/${this.data.github.name}/commit/${commit.hash}`;
return `[${commit.shortHash}](${url})`;
const hash = sanitizeCommitHash(commit.hash);
const shortHash = sanitizeCommitHash(commit.shortHash);
const url = `https://github.com/${this.data.github.owner}/${this.data.github.name}/commit/${hash}`;
return `[${shortHash}](${url})`;
};

/**
Expand Down Expand Up @@ -240,12 +242,19 @@ export class RenderContext {
color = 'orange';
break;
}
const url = `https://github.com/${this.data.github.owner}/${this.data.github.name}/commit/${commit.hash}`;
const imgSrc = `https://img.shields.io/badge/${commit.shortHash}-${commit.type}-${color}`;
return `[![${commit.type} - ${commit.shortHash}](${imgSrc})](${url})`;
const hash = sanitizeCommitHash(commit.hash);
const shortHash = sanitizeCommitHash(commit.shortHash);
const url = `https://github.com/${this.data.github.owner}/${this.data.github.name}/commit/${hash}`;
const imgSrc = `https://img.shields.io/badge/${shortHash}-${commit.type}-${color}`;
return `[![${commit.type} - ${shortHash}](${imgSrc})](${url})`;
};
}

/** Sanitizes a commit hash or shortHash for safe interpolation into Markdown links. */
function sanitizeCommitHash(hash: string | undefined): string {
return (hash ?? '').replace(/[\[\]()<>\\\r\n]/g, '');
}

/**
* Builds a date stamp for stamping in release notes.
*
Expand Down
30 changes: 30 additions & 0 deletions ng-dev/release/publish/test/release-notes/context.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,36 @@ describe('RenderContext', () => {
});
});

describe('commitToLink', () => {
it('creates a markdown link for a commit', () => {
const renderContext = new RenderContext(defaultContextData);
const [commit] = renderContext._categorizeCommits([
{
...buildCommit('fix', 'core'),
hash: '0123456789abcdef',
shortHash: '0123456',
},
]);
expect(renderContext.commitToLink(commit)).toBe(
'[0123456](https://github.com/repoOwner/repoName/commit/0123456789abcdef)',
);
});

it('strips markdown link control characters from shortHash and hash', () => {
const renderContext = new RenderContext(defaultContextData);
const [commit] = renderContext._categorizeCommits([
{
...buildCommit('fix', 'core'),
hash: '0123456789abcdef)(https://evil.example)',
shortHash: '0123456](https://evil.example)[',
},
]);
expect(renderContext.commitToLink(commit)).toBe(
'[0123456https://evil.example](https://github.com/repoOwner/repoName/commit/0123456789abcdefhttps://evil.example)',
);
});
});

afterAll(() => {
jasmine.clock().uninstall();
});
Expand Down
17 changes: 17 additions & 0 deletions ng-dev/release/publish/test/release-notes/generation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -660,4 +660,21 @@ describe('release notes generation', () => {
`| [${shortSha}](https://github.com/angular/dev-infra-test/commit/${fullSha}) | fix | commit *1 |`,
);
});

it('should not split a commit when its body contains a printable delimiter string', async () => {
SandboxGitRepo.withInitialCommit(githubConfig)
.createTagForHead('startTag')
.commit(
'fix(ng-dev): legitimate commit\n\n' +
'-------------ɵɵ------------\n' +
'feat(ng-dev): forged commit from body',
);

const releaseNotes = await ReleaseNotes.forRange(client, parse('13.0.0'), 'startTag', 'HEAD');
const changelog = await releaseNotes.getChangelogEntry();

expect(await releaseNotes.getCommitCountInReleaseNotes()).toBe(1);
expect(changelog).toContain('legitimate commit');
expect(changelog).not.toContain('forged commit from body');
});
});
Loading