Merge pull request #1180 from mikepenz/fix/1176

Introduce `fetchViaCommits` configuration | Offers a new feature to solve an edge case resulting in old PRs not being included in changelog for squash merges
This commit is contained in:
Mike Penz
2023-07-28 12:33:01 +02:00
committed by GitHub
13 changed files with 225 additions and 84 deletions
+1
View File
@@ -333,6 +333,7 @@ For advanced use cases additional settings can be provided to the action
| `includeOpen` | Enables to also fetch currently open PRs. Default: false |
| `ignorePreReleases` | Allows to ignore pre-releases for changelog generation (E.g. for 1.0.1... 1.0.0-rc02 <- ignore, 1.0.0 <- pick). Only used if `fromTag` was not specified. Default: false |
| `failOnError` | Defines if the action will result in a build failure if problems occurred. Default: false |
| `fetchViaCommits` | Enables PRs to get fetched via the commits identified between from->to tag. This will do 1 API request per commit -> Best for scenarios with squash merges | Or shorter from->to diffs (< 10 commits) | Also effective for shorters diffs for very old PRs. Default: false |
| `fetchReviewers` | Will enable fetching the users/reviewers who approved the PR. Default: false |
| `fetchReleaseInformation` | Will enable fetching additional release information from tags. Default: false |
| `fetchReviews` | Will enable fetching the reviews on of the PR. Default: false |
+14
View File
@@ -16,6 +16,7 @@ it('Should match generated changelog (unspecified fromTag)', async () => {
false,
false,
false,
true, // enable to fetch via commits
false, // enable to fetch reviewers
false, // enable to fetch tag release information
false, // enable to fetch reviews
@@ -47,6 +48,7 @@ it('Should match generated changelog (unspecified tags)', async () => {
false,
false,
false,
false, // enable to fetch via commits
false, // enable to fetch reviewers
false, // enable to fetch tag release information
false, // enable to fetch reviews
@@ -73,6 +75,7 @@ it('Should use empty placeholder', async () => {
false,
false,
false,
true, // enable to fetch via commits
false, // enable to fetch reviewers
false, // enable to fetch tag release information
false, // enable to fetch reviews
@@ -99,6 +102,7 @@ it('Should fill empty placeholders', async () => {
false,
false,
false,
true, // enable to fetch via commits
false, // enable to fetch reviewers
false, // enable to fetch tag release information
false, // enable to fetch reviews
@@ -127,6 +131,7 @@ it('Should fill `template` placeholders', async () => {
false,
false,
false,
true, // enable to fetch via commits
false, // enable to fetch reviewers
false, // enable to fetch tag release information
false, // enable to fetch reviews
@@ -156,6 +161,7 @@ it('Should fill `template` placeholders, ignore', async () => {
false,
false,
false,
false, // enable to fetch via commits
false, // enable to fetch reviewers
false, // enable to fetch tag release information
false, // enable to fetch reviews
@@ -184,6 +190,7 @@ it('Uncategorized category', async () => {
false,
false,
false,
false, // enable to fetch via commits
false, // enable to fetch reviewers
false, // enable to fetch tag release information
false, // enable to fetch reviews
@@ -212,6 +219,7 @@ it('Verify commit based changelog', async () => {
false,
false,
false,
true, // enable to fetch via commits
false, // enable to fetch reviewers
false, // enable to fetch tag release information
false, // enable to fetch reviews
@@ -240,6 +248,7 @@ it('Verify commit based changelog, with emoji categorisation', async () => {
false,
false,
false,
false, // enable to fetch via commits
false, // enable to fetch reviewers
false, // enable to fetch tag release information
false, // enable to fetch reviews
@@ -268,6 +277,7 @@ it('Verify default inclusion of open PRs', async () => {
true, // includeOpen
false, // failOnError
false, // ignorePrePrelease
false, // enable to fetch via commits
false, // enable to fetch reviewers
false, // enable to fetch tag release information
false, // enable to fetch reviews
@@ -296,6 +306,7 @@ it('Verify custom categorisation of open PRs', async () => {
true, // includeOpen
false, // failOnError
false, // ignorePrePrelease
false, // enable to fetch via commits
false, // enable to fetch reviewers
false, // enable to fetch tag release information
false, // enable to fetch reviews
@@ -324,6 +335,7 @@ it('Verify reviewers who approved are fetched and also release information', asy
true, // includeOpen
false, // failOnError
false, // ignorePrePrelease
false, // enable to fetch via commits
true, // enable to fetch reviewers
true, // enable to fetch tag release information
false, // enable to fetch reviews
@@ -353,6 +365,7 @@ it('Fetch release information', async () => {
true, // includeOpen
false, // failOnError
false, // ignorePrePrelease
false, // enable to fetch via commits
false, // enable to fetch reviewers
true, // enable to fetch tag release information
false, // enable to fetch reviews
@@ -380,6 +393,7 @@ it('Fetch release information for non existing tag / release', async () => {
true, // includeOpen
false, // failOnError
false, // ignorePrePrelease
false, // enable to fetch via commits
false, // enable to fetch reviewers
true, // enable to fetch tag release information
false, // enable to fetch reviews
+11 -1
View File
@@ -1,7 +1,7 @@
import {mergeConfiguration, resolveConfiguration} from '../src/utils'
import {Octokit} from '@octokit/rest'
import {buildChangelog} from '../src/transform'
import { pullData } from 'github-pr-collector'
import {pullData} from 'github-pr-collector'
jest.setTimeout(180000)
@@ -20,6 +20,7 @@ it('Should have empty changelog (tags)', async () => {
toTag: {name: 'v0.0.2'},
includeOpen: false,
failOnError: false,
fetchViaCommits: true,
fetchReviewers: false,
fetchReleaseInformation: false,
fetchReviews: false,
@@ -41,6 +42,7 @@ it('Should match generated changelog (tags)', async () => {
toTag: {name: 'v0.0.3'},
includeOpen: false,
failOnError: false,
fetchViaCommits: true,
fetchReviewers: false,
fetchReleaseInformation: false,
fetchReviews: false,
@@ -69,6 +71,7 @@ it('Should match generated changelog (refs)', async () => {
toTag: {name: 'fa3788c8c4b3373ef8424ce3eb008a5cd07cc5aa'},
includeOpen: false,
failOnError: false,
fetchViaCommits: true,
fetchReviewers: false,
fetchReleaseInformation: false,
fetchReviews: false,
@@ -104,6 +107,7 @@ it('Should match generated changelog and replace all occurrences (refs)', async
toTag: {name: 'fa3788c8c4b3373ef8424ce3eb008a5cd07cc5aa'},
includeOpen: false,
failOnError: false,
fetchViaCommits: true,
fetchReviewers: false,
fetchReleaseInformation: false,
fetchReviews: false,
@@ -141,6 +145,7 @@ it('Should match ordered ASC', async () => {
toTag: {name: 'v0.5.0'},
includeOpen: false,
failOnError: false,
fetchViaCommits: false,
fetchReviewers: false,
fetchReleaseInformation: false,
fetchReviews: false,
@@ -164,6 +169,7 @@ it('Should match ordered DESC', async () => {
toTag: {name: 'v0.5.0'},
includeOpen: false,
failOnError: false,
fetchViaCommits: false,
fetchReviewers: false,
fetchReleaseInformation: false,
fetchReviews: false,
@@ -186,6 +192,7 @@ it('Should match ordered by title ASC', async () => {
toTag: {name: 'v0.5.0'},
includeOpen: false,
failOnError: false,
fetchViaCommits: false,
fetchReviewers: false,
fetchReleaseInformation: false,
fetchReviews: false,
@@ -209,6 +216,7 @@ it('Should match ordered by title DESC', async () => {
toTag: {name: 'v0.5.0'},
includeOpen: false,
failOnError: false,
fetchViaCommits: false,
fetchReviewers: false,
fetchReleaseInformation: false,
fetchReviews: false,
@@ -233,6 +241,7 @@ it('Should ignore PRs not merged into develop branch', async () => {
toTag: {name: 'v1.4.0'},
includeOpen: false,
failOnError: false,
fetchViaCommits: true,
fetchReviewers: false,
fetchReleaseInformation: false,
fetchReviews: false,
@@ -254,6 +263,7 @@ it('Should ignore PRs not merged into main branch', async () => {
toTag: {name: 'v1.4.0'},
includeOpen: false,
failOnError: false,
fetchViaCommits: true,
fetchReviewers: false,
fetchReleaseInformation: false,
fetchReviews: false,
+1 -1
View File
@@ -1,4 +1,4 @@
import { TagInfo, filterTags, prepareAndSortTags } from "github-pr-collector/lib/tags"
import {TagInfo, filterTags, prepareAndSortTags} from 'github-pr-collector/lib/tags'
jest.setTimeout(180000)
+5 -7
View File
@@ -1,8 +1,8 @@
import {buildChangelog} from '../src/transform'
import moment from 'moment'
import {Configuration, DefaultConfiguration} from '../src/configuration'
import { PullRequestInfo } from 'github-pr-collector/lib/pullRequests'
import { DefaultDiffInfo } from 'github-pr-collector/lib/commits'
import {PullRequestInfo} from 'github-pr-collector/lib/pullRequests'
import {DefaultDiffInfo} from 'github-pr-collector/lib/commits'
jest.setTimeout(180000)
@@ -409,16 +409,14 @@ it('Reference PRs', async () => {
labels: []
}
]
customConfig.pr_template = "${{NUMBER}} -- ${{REFERENCED[*].number}}"
customConfig.pr_template = '${{NUMBER}} -- ${{REFERENCED[*].number}}'
customConfig.reference = {
pattern: '.*\ \#(.).*', // matches the 1 from "abcdefg #1 adfasdf"
pattern: '.* #(.).*', // matches the 1 from "abcdefg #1 adfasdf"
on_property: 'body',
method: 'replace',
target: '$1'
}
expect(buildChangelogTest(customConfig, pullRequestsWithLabels)).toStrictEqual(
`1 -- 2\n4 -- \n3 -- \n\n`
)
expect(buildChangelogTest(customConfig, pullRequestsWithLabels)).toStrictEqual(`1 -- 2\n4 -- \n3 -- \n\n`)
})
it('Use empty_content for empty category', async () => {
+3
View File
@@ -28,6 +28,9 @@ inputs:
failOnError:
description: 'Defines if the action should result in a build failure, if an error was discovered'
default: "false"
fetchViaCommits:
description: 'Defines if PRs are fetched via the commits identified. This will do 1 API request per commit -> Best for scenarios with squash merges | Or shorter from-to diffs (< 10 commits) | Also effective for shorters diffs for very old PRs'
default: "false"
fetchReviewers:
description: 'Will enable fetching the users/reviewers who approved the PR'
default: "false"
Generated Vendored
+104 -42
View File
@@ -141,12 +141,13 @@ function run() {
const includeOpen = core.getInput('includeOpen') === 'true';
const ignorePreReleases = core.getInput('ignorePreReleases') === 'true';
const failOnError = core.getInput('failOnError') === 'true';
const fetchViaCommits = core.getInput('fetchViaCommits') === 'true';
const fetchReviewers = core.getInput('fetchReviewers') === 'true';
const fetchReleaseInformation = core.getInput('fetchReleaseInformation') === 'true';
const fetchReviews = core.getInput('fetchReviews') === 'true';
const commitMode = core.getInput('commitMode') === 'true';
const exportOnly = core.getInput('exportOnly') === 'true';
const result = yield new releaseNotesBuilder_1.ReleaseNotesBuilder(baseUrl, token, repositoryPath, owner, repo, fromTag, toTag, includeOpen, failOnError, ignorePreReleases, fetchReviewers, fetchReleaseInformation, fetchReviews, commitMode, exportOnly, configuration).build();
const result = yield new releaseNotesBuilder_1.ReleaseNotesBuilder(baseUrl, token, repositoryPath, owner, repo, fromTag, toTag, includeOpen, failOnError, ignorePreReleases, fetchViaCommits, fetchReviewers, fetchReleaseInformation, fetchReviews, commitMode, exportOnly, configuration).build();
core.setOutput('changelog', result);
// write the result in changelog to file if possible
const outputFile = core.getInput('outputFile');
@@ -282,7 +283,7 @@ const transform_1 = __nccwpck_require__(1644);
const github_pr_collector_1 = __nccwpck_require__(3196);
const utils_2 = __nccwpck_require__(853);
class ReleaseNotesBuilder {
constructor(baseUrl, token, repositoryPath, owner, repo, fromTag, toTag, includeOpen = false, failOnError, ignorePreReleases, fetchReviewers = false, fetchReleaseInformation = false, fetchReviews = false, commitMode = false, exportOnly = false, configuration) {
constructor(baseUrl, token, repositoryPath, owner, repo, fromTag, toTag, includeOpen = false, failOnError, ignorePreReleases, fetchViaCommits = false, fetchReviewers = false, fetchReleaseInformation = false, fetchReviews = false, commitMode = false, exportOnly = false, configuration) {
this.baseUrl = baseUrl;
this.token = token;
this.repositoryPath = repositoryPath;
@@ -293,6 +294,7 @@ class ReleaseNotesBuilder {
this.includeOpen = includeOpen;
this.failOnError = failOnError;
this.ignorePreReleases = ignorePreReleases;
this.fetchViaCommits = fetchViaCommits;
this.fetchReviewers = fetchReviewers;
this.fetchReleaseInformation = fetchReleaseInformation;
this.fetchReviews = fetchReviews;
@@ -319,7 +321,7 @@ class ReleaseNotesBuilder {
core.debug(`Resolved 'repo' as ${this.repo}`);
}
core.endGroup();
const prData = yield new github_pr_collector_1.PullRequestCollector(this.baseUrl, this.token, this.repositoryPath, this.owner, this.repo, this.fromTag, this.toTag, this.includeOpen, this.failOnError, this.ignorePreReleases, this.fetchReviewers, this.fetchReleaseInformation, this.fetchReviews, this.commitMode, this.configuration).build();
const prData = yield new github_pr_collector_1.PullRequestCollector(this.baseUrl, this.token, this.repositoryPath, this.owner, this.repo, this.fromTag, this.toTag, this.includeOpen, this.failOnError, this.ignorePreReleases, this.fetchViaCommits, this.fetchReviewers, this.fetchReleaseInformation, this.fetchReviews, this.commitMode, this.configuration).build();
if (prData == null) {
return null;
}
@@ -26272,13 +26274,15 @@ class Commits {
commitInfo: commits
.filter(commit => commit.sha)
.map(commit => {
var _a, _b;
var _a, _b, _c, _d;
return ({
sha: commit.sha || '',
summary: commit.commit.message.split('\n')[0],
message: commit.commit.message,
date: (0, moment_1.default)((_a = commit.commit.committer) === null || _a === void 0 ? void 0 : _a.date),
author: ((_b = commit.commit.author) === null || _b === void 0 ? void 0 : _b.name) || '',
author: ((_a = commit.commit.author) === null || _a === void 0 ? void 0 : _a.name) || '',
authorDate: (0, moment_1.default)((_b = commit.commit.author) === null || _b === void 0 ? void 0 : _b.date),
committer: ((_c = commit.commit.committer) === null || _c === void 0 ? void 0 : _c.name) || '',
commitDate: (0, moment_1.default)((_d = commit.commit.committer) === null || _d === void 0 ? void 0 : _d.date),
prNumber: undefined
});
})
@@ -26296,10 +26300,10 @@ class Commits {
commitsResult.push(commit);
}
commitsResult.sort((a, b) => {
if (a.date.isBefore(b.date)) {
if (a.commitDate.isBefore(b.commitDate)) {
return -1;
}
else if (b.date.isBefore(a.date)) {
else if (b.commitDate.isBefore(a.commitDate)) {
return 1;
}
return 0;
@@ -26342,8 +26346,8 @@ class Commits {
title: commit.summary,
htmlURL: '',
baseBranch: '',
createdAt: commit.date,
mergedAt: commit.date,
createdAt: commit.commitDate,
mergedAt: commit.commitDate,
mergeCommitSha: commit.sha,
author: commit.author || '',
repoName: '',
@@ -26555,7 +26559,7 @@ const https_proxy_agent_1 = __nccwpck_require__(7219);
const pullRequests_1 = __nccwpck_require__(1948);
const commits_1 = __nccwpck_require__(5789);
class PullRequestCollector {
constructor(baseUrl, token, repositoryPath, owner, repo, fromTag, toTag, includeOpen = false, failOnError, ignorePreReleases, fetchReviewers = false, fetchReleaseInformation = false, fetchReviews = false, commitMode = false, configuration) {
constructor(baseUrl, token, repositoryPath, owner, repo, fromTag, toTag, includeOpen = false, failOnError, ignorePreReleases, fetchViaCommits = false, fetchReviewers = false, fetchReleaseInformation = false, fetchReviews = false, commitMode = false, configuration) {
this.baseUrl = baseUrl;
this.token = token;
this.repositoryPath = repositoryPath;
@@ -26566,6 +26570,7 @@ class PullRequestCollector {
this.includeOpen = includeOpen;
this.failOnError = failOnError;
this.ignorePreReleases = ignorePreReleases;
this.fetchViaCommits = fetchViaCommits;
this.fetchReviewers = fetchReviewers;
this.fetchReleaseInformation = fetchReleaseInformation;
this.fetchReviews = fetchReviews;
@@ -26630,6 +26635,7 @@ class PullRequestCollector {
toTag: thisTag,
includeOpen: this.includeOpen,
failOnError: this.failOnError,
fetchViaCommits: this.fetchViaCommits,
fetchReviewers: this.fetchReviewers,
fetchReleaseInformation: this.fetchReleaseInformation,
fetchReviews: this.fetchReviews,
@@ -26772,8 +26778,40 @@ class PullRequests {
}
});
}
getBetweenDates(owner, repo, fromDate, toDate, maxPullRequests) {
getForCommitHash(owner, repo, commit_sha, maxPullRequests) {
var _a, e_1, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
const mergedPRs = [];
const options = this.octokit.repos.listPullRequestsAssociatedWithCommit.endpoint.merge({
owner,
repo,
commit_sha,
per_page: `${Math.min(10, maxPullRequests)}`,
direction: 'desc'
});
try {
for (var _d = true, _e = __asyncValues(this.octokit.paginate.iterator(options)), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
_c = _f.value;
_d = false;
const response = _c;
const prs = response.data;
for (const pr of prs) {
mergedPRs.push(mapPullRequest(pr, pr.merged_at ? 'merged' : 'open'));
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
}
finally { if (e_1) throw e_1.error; }
}
return sortPrs(mergedPRs);
});
}
getBetweenDates(owner, repo, fromDate, toDate, maxPullRequests) {
var _a, e_2, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
const mergedPRs = [];
const options = this.octokit.pulls.list.endpoint.merge({
@@ -26801,22 +26839,22 @@ class PullRequests {
core.warning(`⚠️ Reached 'maxPullRequests' count ${maxPullRequests}`);
}
// bail out early to not keep iterating on PRs super old
return sortPrs(mergedPRs);
break;
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
}
finally { if (e_1) throw e_1.error; }
finally { if (e_2) throw e_2.error; }
}
return sortPrs(mergedPRs);
});
}
getOpen(owner, repo, maxPullRequests) {
var _a, e_2, _b, _c;
var _a, e_3, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
const openPrs = [];
const options = this.octokit.pulls.list.endpoint.merge({
@@ -26842,22 +26880,22 @@ class PullRequests {
core.warning(`⚠️ Reached 'maxPullRequests' count ${maxPullRequests}`);
}
// bail out early to not keep iterating on PRs super old
return sortPrs(openPrs);
break;
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
catch (e_3_1) { e_3 = { error: e_3_1 }; }
finally {
try {
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
}
finally { if (e_2) throw e_2.error; }
finally { if (e_3) throw e_3.error; }
}
return sortPrs(openPrs);
});
}
getReviews(owner, repo, pr) {
var _a, e_3, _b, _c;
var _a, e_4, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
const options = this.octokit.pulls.listReviews.endpoint.merge({
owner,
@@ -26878,12 +26916,12 @@ class PullRequests {
}
}
}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
catch (e_4_1) { e_4 = { error: e_4_1 }; }
finally {
try {
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
}
finally { if (e_3) throw e_3.error; }
finally { if (e_4) throw e_4.error; }
}
pr.reviews = prReviews;
});
@@ -26898,8 +26936,8 @@ class PullRequests {
}
const firstCommit = commits[0];
const lastCommit = commits[commits.length - 1];
let fromDate = firstCommit.date;
const toDate = lastCommit.date;
let fromDate = moment_1.default.min(firstCommit.authorDate, firstCommit.commitDate); // get the lower date (e.g. if commits are modified)
const toDate = moment_1.default.max(lastCommit.authorDate, lastCommit.commitDate); // ensure we get the higher date (e.g. in case of rebases)
const maxDays = configuration.max_back_track_time_days;
const maxFromDate = toDate.clone().subtract(maxDays, 'days');
if (maxFromDate.isAfter(fromDate)) {
@@ -26907,27 +26945,51 @@ class PullRequests {
fromDate = maxFromDate;
}
core.info(`️ Fetching PRs between dates ${fromDate.toISOString()} to ${toDate.toISOString()} for ${owner}/${repo}`);
const pullRequests = yield this.getBetweenDates(owner, repo, fromDate, toDate, configuration.max_pull_requests);
core.info(`️ Retrieved ${pullRequests.length} PRs for ${owner}/${repo} in date range from API`);
const prCommits = (0, commits_1.filterCommits)(commits, configuration.exclude_merge_branches);
core.info(`️ Retrieved ${prCommits.length} release commits for ${owner}/${repo}`);
// create array of commits for this release
const releaseCommitHashes = prCommits.map(commmit => {
return commmit.sha;
const releaseCommitHashes = prCommits.map(commit => {
return commit.sha;
});
// filter out pull requests not associated with this release
const mergedPullRequests = pullRequests.filter(pr => {
return releaseCommitHashes.includes(pr.mergeCommitSha);
});
core.info(`️ Retrieved ${mergedPullRequests.length} merged PRs for ${owner}/${repo}`);
let allPullRequests = mergedPullRequests;
if (includeOpen) {
// retrieve all open pull requests
const openPullRequests = yield this.getOpen(owner, repo, configuration.max_pull_requests);
core.info(`️ Retrieved ${openPullRequests.length} open PRs for ${owner}/${repo}`);
// all pull requests
allPullRequests = allPullRequests.concat(openPullRequests);
core.info(`️ Retrieved ${allPullRequests.length} total PRs for ${owner}/${repo}`);
let pullRequests;
if (options.fetchViaCommits) {
// fetch PRs based on commits instead (will get associated PRs per commit found)
const prsForReleaseCommits = new Map();
for (const commit of prCommits) {
const result = yield this.getForCommitHash(owner, repo, commit.sha, configuration.max_pull_requests);
for (const pr of result) {
prsForReleaseCommits.set(pr.number, pr);
}
}
const dedupedPrsForReleaseCommits = Array.from(prsForReleaseCommits.values());
if (!includeOpen) {
pullRequests = dedupedPrsForReleaseCommits.filter(pr => pr.status !== 'open');
core.info(`️ Retrieved ${pullRequests.length} PRs for ${owner}/${repo} based on the release commit hashes`);
}
else {
pullRequests = dedupedPrsForReleaseCommits;
core.info(`️ Retrieved ${pullRequests.length} PRs for ${owner}/${repo} based on the release commit hashes (including open)`);
}
}
else {
// fetch PRs based on the date range identified
const pullRequestsBetweenDate = yield this.getBetweenDates(owner, repo, fromDate, toDate, configuration.max_pull_requests);
core.info(`️ Retrieved ${pullRequestsBetweenDate.length} PRs for ${owner}/${repo} in date range from API`);
// filter out pull requests not associated with this release
const mergedPullRequests = pullRequestsBetweenDate.filter(pr => {
return releaseCommitHashes.includes(pr.mergeCommitSha);
});
core.info(`️ Retrieved ${mergedPullRequests.length} merged PRs for ${owner}/${repo}`);
let allPullRequests = mergedPullRequests;
if (includeOpen) {
// retrieve all open pull requests
const openPullRequests = yield this.getOpen(owner, repo, configuration.max_pull_requests);
core.info(`️ Retrieved ${openPullRequests.length} open PRs for ${owner}/${repo}`);
// all pull requests
allPullRequests = allPullRequests.concat(openPullRequests);
core.info(`️ Retrieved ${allPullRequests.length} total PRs for ${owner}/${repo}`);
}
pullRequests = allPullRequests;
}
// retrieve base branches we allow
const baseBranches = configuration.base_branches;
@@ -26935,7 +26997,7 @@ class PullRequests {
return new RegExp(baseBranch.replace('\\\\', '\\'), 'gu');
});
// return only prs if the baseBranch is matching the configuration
const finalPrs = allPullRequests.filter(pr => {
const finalPrs = pullRequests.filter(pr => {
if (baseBranches.length !== 0) {
return baseBranchPatterns.some(pattern => {
return pr.baseBranch.match(pattern) !== null;
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
+10 -6
View File
@@ -28,7 +28,9 @@ export interface CommitInfo {
summary: string
message: string
author: string
date: moment.Moment
authorDate: moment.Moment
committer: string
commitDate: moment.Moment
}
export class Commits {
@@ -90,8 +92,10 @@ export class Commits {
sha: commit.sha || '',
summary: commit.commit.message.split('\n')[0],
message: commit.commit.message,
date: moment(commit.commit.committer?.date),
author: commit.commit.author?.name || '',
authorDate: moment(commit.commit.author?.date),
committer: commit.commit.committer?.name || '',
commitDate: moment(commit.commit.committer?.date),
prNumber: undefined
}))
}
@@ -110,9 +114,9 @@ export class Commits {
}
commitsResult.sort((a, b) => {
if (a.date.isBefore(b.date)) {
if (a.commitDate.isBefore(b.commitDate)) {
return -1
} else if (b.date.isBefore(a.date)) {
} else if (b.commitDate.isBefore(a.commitDate)) {
return 1
}
return 0
@@ -160,8 +164,8 @@ export class Commits {
title: commit.summary,
htmlURL: '',
baseBranch: '',
createdAt: commit.date,
mergedAt: commit.date,
createdAt: commit.commitDate,
mergedAt: commit.commitDate,
mergeCommitSha: commit.sha,
author: commit.author || '',
repoName: '',
+3
View File
@@ -14,6 +14,7 @@ export interface Options {
toTag: TagInfo // the tag/ref up to
includeOpen: boolean // defines if we should also fetch open pull requests
failOnError: boolean // defines if we should fail the action in case of an error
fetchViaCommits: boolean // defines if PRs are fetched via the commits identified. This will do 1 API request per commit -> Best for scenarios with squash merges | Or shorter from-to diffs (< 10 commits) | Also effective for shorters diffs for very old PRs
fetchReviewers: boolean // defines if the action should fetch the reviewers for PRs - approved reviewers are not included in the default PR listing
fetchReleaseInformation: boolean // defines if the action should fetch the release information for the from and to tag - e.g. the creation date for the associated release
fetchReviews: boolean // defines if the action should fetch the reviews for the PR.
@@ -40,6 +41,7 @@ export class PullRequestCollector {
private includeOpen: boolean = false,
private failOnError: boolean,
private ignorePreReleases: boolean,
private fetchViaCommits: boolean = false,
private fetchReviewers: boolean = false,
private fetchReleaseInformation: boolean = false,
private fetchReviews: boolean = false,
@@ -119,6 +121,7 @@ export class PullRequestCollector {
toTag: thisTag,
includeOpen: this.includeOpen,
failOnError: this.failOnError,
fetchViaCommits: this.fetchViaCommits,
fetchReviewers: this.fetchReviewers,
fetchReleaseInformation: this.fetchReleaseInformation,
fetchReviews: this.fetchReviews,
+67 -25
View File
@@ -91,6 +91,28 @@ export class PullRequests {
}
}
async getForCommitHash(owner: string, repo: string, commit_sha: string, maxPullRequests: number): Promise<PullRequestInfo[]> {
const mergedPRs: PullRequestInfo[] = []
const options = this.octokit.repos.listPullRequestsAssociatedWithCommit.endpoint.merge({
owner,
repo,
commit_sha,
per_page: `${Math.min(10, maxPullRequests)}`,
direction: 'desc'
})
for await (const response of this.octokit.paginate.iterator(options)) {
const prs: PullsListData = response.data as PullsListData
for (const pr of prs) {
mergedPRs.push(mapPullRequest(pr, pr.merged_at ? 'merged' : 'open'))
}
}
return sortPrs(mergedPRs)
}
async getBetweenDates(
owner: string,
repo: string,
@@ -126,7 +148,7 @@ export class PullRequests {
}
// bail out early to not keep iterating on PRs super old
return sortPrs(mergedPRs)
break
}
}
@@ -158,7 +180,7 @@ export class PullRequests {
}
// bail out early to not keep iterating on PRs super old
return sortPrs(openPrs)
break
}
}
@@ -195,8 +217,8 @@ export class PullRequests {
const firstCommit = commits[0]
const lastCommit = commits[commits.length - 1]
let fromDate = firstCommit.date
const toDate = lastCommit.date
let fromDate = moment.min(firstCommit.authorDate, firstCommit.commitDate) // get the lower date (e.g. if commits are modified)
const toDate = moment.max(lastCommit.authorDate, lastCommit.commitDate) // ensure we get the higher date (e.g. in case of rebases)
const maxDays = configuration.max_back_track_time_days
const maxFromDate = toDate.clone().subtract(maxDays, 'days')
@@ -207,37 +229,57 @@ export class PullRequests {
core.info(`️ Fetching PRs between dates ${fromDate.toISOString()} to ${toDate.toISOString()} for ${owner}/${repo}`)
const pullRequests = await this.getBetweenDates(owner, repo, fromDate, toDate, configuration.max_pull_requests)
core.info(`️ Retrieved ${pullRequests.length} PRs for ${owner}/${repo} in date range from API`)
const prCommits = filterCommits(commits, configuration.exclude_merge_branches)
core.info(`️ Retrieved ${prCommits.length} release commits for ${owner}/${repo}`)
// create array of commits for this release
const releaseCommitHashes = prCommits.map(commmit => {
return commmit.sha
const releaseCommitHashes = prCommits.map(commit => {
return commit.sha
})
// filter out pull requests not associated with this release
const mergedPullRequests = pullRequests.filter(pr => {
return releaseCommitHashes.includes(pr.mergeCommitSha)
})
let pullRequests: PullRequestInfo[]
if (options.fetchViaCommits) {
// fetch PRs based on commits instead (will get associated PRs per commit found)
const prsForReleaseCommits: Map<number, PullRequestInfo> = new Map()
for (const commit of prCommits) {
const result = await this.getForCommitHash(owner, repo, commit.sha, configuration.max_pull_requests)
for (const pr of result) {
prsForReleaseCommits.set(pr.number, pr)
}
}
const dedupedPrsForReleaseCommits = Array.from(prsForReleaseCommits.values())
if (!includeOpen) {
pullRequests = dedupedPrsForReleaseCommits.filter(pr => pr.status !== 'open')
core.info(`️ Retrieved ${pullRequests.length} PRs for ${owner}/${repo} based on the release commit hashes`)
} else {
pullRequests = dedupedPrsForReleaseCommits
core.info(`️ Retrieved ${pullRequests.length} PRs for ${owner}/${repo} based on the release commit hashes (including open)`)
}
} else {
// fetch PRs based on the date range identified
const pullRequestsBetweenDate = await this.getBetweenDates(owner, repo, fromDate, toDate, configuration.max_pull_requests)
core.info(`️ Retrieved ${pullRequestsBetweenDate.length} PRs for ${owner}/${repo} in date range from API`)
core.info(`️ Retrieved ${mergedPullRequests.length} merged PRs for ${owner}/${repo}`)
// filter out pull requests not associated with this release
const mergedPullRequests = pullRequestsBetweenDate.filter(pr => {
return releaseCommitHashes.includes(pr.mergeCommitSha)
})
let allPullRequests = mergedPullRequests
if (includeOpen) {
// retrieve all open pull requests
const openPullRequests = await this.getOpen(owner, repo, configuration.max_pull_requests)
core.info(`️ Retrieved ${mergedPullRequests.length} merged PRs for ${owner}/${repo}`)
core.info(`️ Retrieved ${openPullRequests.length} open PRs for ${owner}/${repo}`)
let allPullRequests = mergedPullRequests
if (includeOpen) {
// retrieve all open pull requests
const openPullRequests = await this.getOpen(owner, repo, configuration.max_pull_requests)
// all pull requests
allPullRequests = allPullRequests.concat(openPullRequests)
core.info(`️ Retrieved ${openPullRequests.length} open PRs for ${owner}/${repo}`)
core.info(`️ Retrieved ${allPullRequests.length} total PRs for ${owner}/${repo}`)
// all pull requests
allPullRequests = allPullRequests.concat(openPullRequests)
core.info(`️ Retrieved ${allPullRequests.length} total PRs for ${owner}/${repo}`)
}
pullRequests = allPullRequests
}
// retrieve base branches we allow
@@ -247,7 +289,7 @@ export class PullRequests {
})
// return only prs if the baseBranch is matching the configuration
const finalPrs = allPullRequests.filter(pr => {
const finalPrs = pullRequests.filter(pr => {
if (baseBranches.length !== 0) {
return baseBranchPatterns.some(pattern => {
return pr.baseBranch.match(pattern) !== null
+2
View File
@@ -50,6 +50,7 @@ async function run(): Promise<void> {
const includeOpen = core.getInput('includeOpen') === 'true'
const ignorePreReleases = core.getInput('ignorePreReleases') === 'true'
const failOnError = core.getInput('failOnError') === 'true'
const fetchViaCommits = core.getInput('fetchViaCommits') === 'true'
const fetchReviewers = core.getInput('fetchReviewers') === 'true'
const fetchReleaseInformation = core.getInput('fetchReleaseInformation') === 'true'
const fetchReviews = core.getInput('fetchReviews') === 'true'
@@ -67,6 +68,7 @@ async function run(): Promise<void> {
includeOpen,
failOnError,
ignorePreReleases,
fetchViaCommits,
fetchReviewers,
fetchReleaseInformation,
fetchReviews,
+2
View File
@@ -40,6 +40,7 @@ export class ReleaseNotesBuilder {
private includeOpen: boolean = false,
private failOnError: boolean,
private ignorePreReleases: boolean,
private fetchViaCommits: boolean = false,
private fetchReviewers: boolean = false,
private fetchReleaseInformation: boolean = false,
private fetchReviews: boolean = false,
@@ -77,6 +78,7 @@ export class ReleaseNotesBuilder {
this.includeOpen,
this.failOnError,
this.ignorePreReleases,
this.fetchViaCommits,
this.fetchReviewers,
this.fetchReleaseInformation,
this.fetchReviews,