style: code formatting
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import {Rule, Extractor, Regex, Transformer, Sort, PullConfiguration} from './pr-collector/types'
|
||||
import {Extractor, PullConfiguration, Regex, Rule, Sort, Transformer} from './pr-collector/types'
|
||||
|
||||
export interface Configuration extends PullConfiguration {
|
||||
max_tags_to_fetch: number
|
||||
|
||||
+118
-120
@@ -1,155 +1,153 @@
|
||||
import * as core from '@actions/core'
|
||||
import {Octokit, RestEndpointMethodTypes} from '@octokit/rest'
|
||||
import moment from 'moment'
|
||||
import {failOrError} from './utils'
|
||||
import {PullRequestInfo} from './pullRequests'
|
||||
import {Options} from './prCollector'
|
||||
import {BaseRepository} from "../repositories/BaseRepository";
|
||||
import {BaseRepository} from '../repositories/BaseRepository'
|
||||
|
||||
export interface DiffInfo {
|
||||
changedFiles: number
|
||||
additions: number
|
||||
deletions: number
|
||||
changes: number
|
||||
commits: number
|
||||
commitInfo: CommitInfo[]
|
||||
changedFiles: number
|
||||
additions: number
|
||||
deletions: number
|
||||
changes: number
|
||||
commits: number
|
||||
commitInfo: CommitInfo[]
|
||||
}
|
||||
|
||||
export const DefaultDiffInfo: DiffInfo = {
|
||||
changedFiles: 0,
|
||||
additions: 0,
|
||||
deletions: 0,
|
||||
changes: 0,
|
||||
commits: 0,
|
||||
commitInfo: []
|
||||
changedFiles: 0,
|
||||
additions: 0,
|
||||
deletions: 0,
|
||||
changes: 0,
|
||||
commits: 0,
|
||||
commitInfo: []
|
||||
}
|
||||
|
||||
export interface CommitInfo {
|
||||
sha: string
|
||||
summary: string
|
||||
message: string
|
||||
author: string
|
||||
authorDate: moment.Moment
|
||||
committer: string
|
||||
commitDate: moment.Moment
|
||||
sha: string
|
||||
summary: string
|
||||
message: string
|
||||
author: string
|
||||
authorDate: moment.Moment
|
||||
committer: string
|
||||
commitDate: moment.Moment
|
||||
}
|
||||
|
||||
export class Commits {
|
||||
constructor(private repositoryUtils: BaseRepository) {
|
||||
constructor(private repositoryUtils: BaseRepository) {}
|
||||
|
||||
async getDiff(owner: string, repo: string, base: string, head: string): Promise<DiffInfo> {
|
||||
const diff: DiffInfo = await this.getDiffRemote(owner, repo, base, head)
|
||||
diff.commitInfo = this.sortCommits(diff.commitInfo)
|
||||
return diff
|
||||
}
|
||||
|
||||
private async getDiffRemote(owner: string, repo: string, base: string, head: string): Promise<DiffInfo> {
|
||||
return this.repositoryUtils.getDiffRemote(owner, repo, base, head)
|
||||
}
|
||||
|
||||
private sortCommits(commits: CommitInfo[]): CommitInfo[] {
|
||||
const commitsResult = []
|
||||
const shas: {[key: string]: boolean} = {}
|
||||
|
||||
for (const commit of commits) {
|
||||
if (shas[commit.sha]) {
|
||||
continue
|
||||
}
|
||||
shas[commit.sha] = true
|
||||
commitsResult.push(commit)
|
||||
}
|
||||
|
||||
async getDiff(owner: string, repo: string, base: string, head: string): Promise<DiffInfo> {
|
||||
const diff: DiffInfo = await this.getDiffRemote(owner, repo, base, head)
|
||||
diff.commitInfo = this.sortCommits(diff.commitInfo)
|
||||
return diff
|
||||
commitsResult.sort((a, b) => {
|
||||
if (a.commitDate.isBefore(b.commitDate)) {
|
||||
return -1
|
||||
} else if (b.commitDate.isBefore(a.commitDate)) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
|
||||
return commitsResult
|
||||
}
|
||||
|
||||
async getCommitHistory(options: Options): Promise<DiffInfo> {
|
||||
const {owner, repo, fromTag, toTag, failOnError} = options
|
||||
core.info(`ℹ️ Comparing ${owner}/${repo} - '${fromTag.name}...${toTag.name}'`)
|
||||
|
||||
const commitsApi = new Commits(this.repositoryUtils)
|
||||
let diffInfo: DiffInfo
|
||||
try {
|
||||
diffInfo = await commitsApi.getDiff(owner, repo, fromTag.name, toTag.name)
|
||||
} catch (error) {
|
||||
failOrError(`💥 Failed to retrieve - Invalid tag? - Because of: ${error}`, failOnError)
|
||||
return DefaultDiffInfo
|
||||
}
|
||||
if (diffInfo.commitInfo.length === 0) {
|
||||
core.warning(`⚠️ No commits found between - ${fromTag.name}...${toTag.name}`)
|
||||
return DefaultDiffInfo
|
||||
}
|
||||
|
||||
private async getDiffRemote(owner: string, repo: string, base: string, head: string): Promise<DiffInfo> {
|
||||
return this.repositoryUtils.getDiffRemote(owner, repo, base, head)
|
||||
return diffInfo
|
||||
}
|
||||
|
||||
async generateCommitPRs(options: Options): Promise<[DiffInfo, PullRequestInfo[]]> {
|
||||
const {owner, repo, configuration} = options
|
||||
|
||||
const diffInfo = await this.getCommitHistory(options)
|
||||
const commits = diffInfo.commitInfo
|
||||
if (commits.length === 0) {
|
||||
return [diffInfo, []]
|
||||
}
|
||||
|
||||
private sortCommits(commits: CommitInfo[]): CommitInfo[] {
|
||||
const commitsResult = []
|
||||
const shas: { [key: string]: boolean } = {}
|
||||
const prCommits = filterCommits(commits, configuration.exclude_merge_branches)
|
||||
|
||||
for (const commit of commits) {
|
||||
if (shas[commit.sha]) {
|
||||
continue
|
||||
}
|
||||
shas[commit.sha] = true
|
||||
commitsResult.push(commit)
|
||||
}
|
||||
core.info(`ℹ️ Retrieved ${prCommits.length} commits for ${owner}/${repo}`)
|
||||
|
||||
commitsResult.sort((a, b) => {
|
||||
if (a.commitDate.isBefore(b.commitDate)) {
|
||||
return -1
|
||||
} else if (b.commitDate.isBefore(a.commitDate)) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
|
||||
return commitsResult
|
||||
}
|
||||
|
||||
async getCommitHistory(options: Options): Promise<DiffInfo> {
|
||||
const {owner, repo, fromTag, toTag, failOnError} = options
|
||||
core.info(`ℹ️ Comparing ${owner}/${repo} - '${fromTag.name}...${toTag.name}'`)
|
||||
|
||||
const commitsApi = new Commits(this.repositoryUtils)
|
||||
let diffInfo: DiffInfo
|
||||
try {
|
||||
diffInfo = await commitsApi.getDiff(owner, repo, fromTag.name, toTag.name)
|
||||
} catch (error) {
|
||||
failOrError(`💥 Failed to retrieve - Invalid tag? - Because of: ${error}`, failOnError)
|
||||
return DefaultDiffInfo
|
||||
}
|
||||
if (diffInfo.commitInfo.length === 0) {
|
||||
core.warning(`⚠️ No commits found between - ${fromTag.name}...${toTag.name}`)
|
||||
return DefaultDiffInfo
|
||||
}
|
||||
|
||||
return diffInfo
|
||||
}
|
||||
|
||||
async generateCommitPRs(options: Options): Promise<[DiffInfo, PullRequestInfo[]]> {
|
||||
const {owner, repo, configuration} = options
|
||||
|
||||
const diffInfo = await this.getCommitHistory(options)
|
||||
const commits = diffInfo.commitInfo
|
||||
if (commits.length === 0) {
|
||||
return [diffInfo, []]
|
||||
}
|
||||
|
||||
const prCommits = filterCommits(commits, configuration.exclude_merge_branches)
|
||||
|
||||
core.info(`ℹ️ Retrieved ${prCommits.length} commits for ${owner}/${repo}`)
|
||||
|
||||
const prs = prCommits.map(function (commit): PullRequestInfo {
|
||||
return {
|
||||
number: 0,
|
||||
title: commit.summary,
|
||||
htmlURL: '',
|
||||
baseBranch: '',
|
||||
createdAt: commit.commitDate,
|
||||
mergedAt: commit.commitDate,
|
||||
mergeCommitSha: commit.sha,
|
||||
author: commit.author || '',
|
||||
repoName: '',
|
||||
labels: [],
|
||||
milestone: '',
|
||||
body: commit.message || '',
|
||||
assignees: [],
|
||||
requestedReviewers: [],
|
||||
approvedReviewers: [],
|
||||
status: 'merged'
|
||||
}
|
||||
})
|
||||
return [diffInfo, prs]
|
||||
}
|
||||
const prs = prCommits.map(function (commit): PullRequestInfo {
|
||||
return {
|
||||
number: 0,
|
||||
title: commit.summary,
|
||||
htmlURL: '',
|
||||
baseBranch: '',
|
||||
createdAt: commit.commitDate,
|
||||
mergedAt: commit.commitDate,
|
||||
mergeCommitSha: commit.sha,
|
||||
author: commit.author || '',
|
||||
repoName: '',
|
||||
labels: [],
|
||||
milestone: '',
|
||||
body: commit.message || '',
|
||||
assignees: [],
|
||||
requestedReviewers: [],
|
||||
approvedReviewers: [],
|
||||
status: 'merged'
|
||||
}
|
||||
})
|
||||
return [diffInfo, prs]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters out all commits which match the exclude pattern
|
||||
*/
|
||||
export function filterCommits(commits: CommitInfo[], excludeMergeBranches: string[]): CommitInfo[] {
|
||||
const filteredCommits = []
|
||||
const filteredCommits = []
|
||||
|
||||
for (const commit of commits) {
|
||||
if (excludeMergeBranches) {
|
||||
let matched = false
|
||||
for (const excludeMergeBranch of excludeMergeBranches) {
|
||||
if (commit.summary.includes(excludeMergeBranch)) {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (matched) {
|
||||
continue
|
||||
}
|
||||
for (const commit of commits) {
|
||||
if (excludeMergeBranches) {
|
||||
let matched = false
|
||||
for (const excludeMergeBranch of excludeMergeBranches) {
|
||||
if (commit.summary.includes(excludeMergeBranch)) {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
filteredCommits.push(commit)
|
||||
}
|
||||
if (matched) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
filteredCommits.push(commit)
|
||||
}
|
||||
|
||||
return filteredCommits
|
||||
return filteredCommits
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ class GitCommandManager {
|
||||
return result
|
||||
}
|
||||
|
||||
async execGit(args: string[], allowAllExitCodes = false, silent = false): Promise<GitOutput> {
|
||||
async execGit(args: string[], allowAllExitCodes = false, silent = false): Promise<GitOutput> {
|
||||
directoryExistsSync(this.workingDirectory, true)
|
||||
|
||||
const result = new GitOutput()
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import * as core from '@actions/core'
|
||||
import {PullConfiguration} from './types'
|
||||
import {Octokit} from '@octokit/rest'
|
||||
import {TagInfo, Tags} from './tags'
|
||||
import {failOrError} from './utils'
|
||||
import {HttpsProxyAgent} from 'https-proxy-agent'
|
||||
import {PullRequestInfo, PullRequests} from './pullRequests'
|
||||
import {Commits, DiffInfo} from './commits'
|
||||
import {BaseRepository} from "../repositories/BaseRepository";
|
||||
import {BaseRepository} from '../repositories/BaseRepository'
|
||||
|
||||
export interface Options {
|
||||
owner: string // the owner of the repository
|
||||
@@ -33,7 +31,7 @@ export interface Data {
|
||||
export class PullRequestCollector {
|
||||
constructor(
|
||||
private baseUrl: string | null,
|
||||
private repositoryUtils: BaseRepository ,
|
||||
private repositoryUtils: BaseRepository,
|
||||
private repositoryPath: string,
|
||||
private owner: string,
|
||||
private repo: string,
|
||||
@@ -47,7 +45,7 @@ export class PullRequestCollector {
|
||||
private fetchReleaseInformation = false,
|
||||
private fetchReviews = false,
|
||||
private commitMode = false,
|
||||
private configuration: PullConfiguration,
|
||||
private configuration: PullConfiguration
|
||||
) {}
|
||||
|
||||
async build(): Promise<Data | null> {
|
||||
@@ -93,7 +91,7 @@ export class PullRequestCollector {
|
||||
|
||||
core.endGroup()
|
||||
|
||||
return await pullData( this.repositoryUtils, {
|
||||
return await pullData(this.repositoryUtils, {
|
||||
owner: this.owner,
|
||||
repo: this.repo,
|
||||
fromTag: previousTag,
|
||||
@@ -110,7 +108,7 @@ export class PullRequestCollector {
|
||||
}
|
||||
}
|
||||
|
||||
export async function pullData( repositoryUtils: BaseRepository , options: Options): Promise<Data | null> {
|
||||
export async function pullData(repositoryUtils: BaseRepository, options: Options): Promise<Data | null> {
|
||||
let mergedPullRequests: PullRequestInfo[]
|
||||
let diffInfo: DiffInfo
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import * as core from '@actions/core'
|
||||
import {Octokit, RestEndpointMethodTypes} from '@octokit/rest'
|
||||
import {Unpacked} from './utils'
|
||||
import {RestEndpointMethodTypes} from '@octokit/rest'
|
||||
import moment from 'moment'
|
||||
import {Property, Sort} from './types'
|
||||
import {Commits, DiffInfo, filterCommits} from './commits'
|
||||
import {Options} from './prCollector'
|
||||
import {BaseRepository} from "../repositories/BaseRepository";
|
||||
import {BaseRepository} from '../repositories/BaseRepository'
|
||||
|
||||
export interface PullRequestInfo {
|
||||
number: number
|
||||
@@ -65,7 +64,7 @@ export const EMPTY_COMMENT_INFO: CommentInfo = {
|
||||
state: undefined
|
||||
}
|
||||
|
||||
export type PullData = RestEndpointMethodTypes['pulls']['get']['response']['data']
|
||||
export type PullData = RestEndpointMethodTypes['pulls']['get']['response']['data']
|
||||
|
||||
export type PullsListData = RestEndpointMethodTypes['pulls']['list']['response']['data']
|
||||
|
||||
@@ -77,17 +76,22 @@ export class PullRequests {
|
||||
private commits: Commits
|
||||
) {}
|
||||
|
||||
|
||||
async getForCommitHash(owner: string, repo: string, commit_sha: string, maxPullRequests: number): Promise<PullRequestInfo[]> {
|
||||
return sortPrs(await this.repositoryUtils.getForCommitHash(owner, repo, commit_sha, maxPullRequests))
|
||||
}
|
||||
|
||||
async getBetweenDates(owner: string, repo: string, fromDate: moment.Moment, toDate: moment.Moment, maxPullRequests: number): Promise<PullRequestInfo[]> {
|
||||
return sortPrs(await this.repositoryUtils.getBetweenDates(owner, repo,fromDate,toDate,maxPullRequests))
|
||||
async getBetweenDates(
|
||||
owner: string,
|
||||
repo: string,
|
||||
fromDate: moment.Moment,
|
||||
toDate: moment.Moment,
|
||||
maxPullRequests: number
|
||||
): Promise<PullRequestInfo[]> {
|
||||
return sortPrs(await this.repositoryUtils.getBetweenDates(owner, repo, fromDate, toDate, maxPullRequests))
|
||||
}
|
||||
|
||||
async getOpen(owner: string, repo: string, maxPullRequests: number): Promise<PullRequestInfo[]> {
|
||||
return sortPrs(await this.repositoryUtils.getOpen(owner, repo,maxPullRequests))
|
||||
return sortPrs(await this.repositoryUtils.getOpen(owner, repo, maxPullRequests))
|
||||
}
|
||||
|
||||
async getReviews(owner: string, repo: string, pr: PullRequestInfo): Promise<void> {
|
||||
@@ -215,8 +219,6 @@ export class PullRequests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function sortPrs(pullRequests: PullRequestInfo[]): PullRequestInfo[] {
|
||||
return sortPullRequests(pullRequests, {
|
||||
order: 'ASC',
|
||||
@@ -281,4 +283,3 @@ export function retrieveProperty(pr: PullRequestInfo, property: Property, useCas
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import {RegexTransformer, TagResolver, Transformer} from './types'
|
||||
import {createCommandManager} from './gitHelper'
|
||||
import moment from 'moment'
|
||||
import {validateTransformer} from './regexUtils'
|
||||
import {BaseRepository} from "../repositories/BaseRepository";
|
||||
import {BaseRepository} from '../repositories/BaseRepository'
|
||||
|
||||
export interface TagResult {
|
||||
from: TagInfo | null
|
||||
@@ -28,11 +28,11 @@ export class Tags {
|
||||
constructor(private repositoryUtils: BaseRepository) {}
|
||||
|
||||
async getTags(owner: string, repo: string, maxTagsToFetch: number): Promise<TagInfo[]> {
|
||||
return this.repositoryUtils.getTags(owner,repo,maxTagsToFetch)
|
||||
return this.repositoryUtils.getTags(owner, repo, maxTagsToFetch)
|
||||
}
|
||||
|
||||
async fillTagInformation(repositoryPath: string, owner: string, repo: string, tagInfo: TagInfo): Promise<TagInfo> {
|
||||
return this.repositoryUtils.fillTagInformation(repositoryPath,owner,repo,tagInfo)
|
||||
return this.repositoryUtils.fillTagInformation(repositoryPath, owner, repo, tagInfo)
|
||||
}
|
||||
|
||||
async findPredecessorTag(
|
||||
|
||||
+36
-36
@@ -1,63 +1,63 @@
|
||||
export interface PullConfiguration {
|
||||
max_tags_to_fetch: number
|
||||
max_pull_requests: number
|
||||
max_back_track_time_days: number
|
||||
exclude_merge_branches: string[]
|
||||
sort: Sort | string // "ASC" or "DESC"
|
||||
tag_resolver: TagResolver
|
||||
base_branches: string[]
|
||||
max_tags_to_fetch: number
|
||||
max_pull_requests: number
|
||||
max_back_track_time_days: number
|
||||
exclude_merge_branches: string[]
|
||||
sort: Sort | string // "ASC" or "DESC"
|
||||
tag_resolver: TagResolver
|
||||
base_branches: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the properties of the PullRequestInfo useable in different configurations
|
||||
*/
|
||||
export type Property =
|
||||
| 'number'
|
||||
| 'title'
|
||||
| 'branch'
|
||||
| 'author'
|
||||
| 'labels'
|
||||
| 'milestone'
|
||||
| 'body'
|
||||
| 'assignees'
|
||||
| 'requestedReviewers'
|
||||
| 'approvedReviewers'
|
||||
| 'status'
|
||||
| 'number'
|
||||
| 'title'
|
||||
| 'branch'
|
||||
| 'author'
|
||||
| 'labels'
|
||||
| 'milestone'
|
||||
| 'body'
|
||||
| 'assignees'
|
||||
| 'requestedReviewers'
|
||||
| 'approvedReviewers'
|
||||
| 'status'
|
||||
|
||||
export interface Rule extends Regex {
|
||||
on_property?: Property // retrieve the property to apply the rule on
|
||||
on_property?: Property // retrieve the property to apply the rule on
|
||||
}
|
||||
|
||||
export interface Sort {
|
||||
order: 'ASC' | 'DESC' // the sorting order
|
||||
on_property: 'mergedAt' | 'title' // the property to sort on. (mergedAt falls back to createdAt)
|
||||
order: 'ASC' | 'DESC' // the sorting order
|
||||
on_property: 'mergedAt' | 'title' // the property to sort on. (mergedAt falls back to createdAt)
|
||||
}
|
||||
|
||||
export interface TagResolver {
|
||||
method: string // semver, sort
|
||||
filter?: Regex // the regex to filter the tags, prior to sorting
|
||||
transformer?: Transformer | Transformer[] // transforms the tag name using the regex, run after the filter
|
||||
method: string // semver, sort
|
||||
filter?: Regex // the regex to filter the tags, prior to sorting
|
||||
transformer?: Transformer | Transformer[] // transforms the tag name using the regex, run after the filter
|
||||
}
|
||||
|
||||
export interface Regex {
|
||||
pattern: string // the regex pattern to match
|
||||
flags?: string // the regex flag to use for RegExp
|
||||
pattern: string // the regex pattern to match
|
||||
flags?: string // the regex flag to use for RegExp
|
||||
}
|
||||
|
||||
export interface Transformer extends Regex {
|
||||
target?: string // the target string to transform the source string using the regex to
|
||||
target?: string // the target string to transform the source string using the regex to
|
||||
}
|
||||
|
||||
export interface Extractor extends Transformer {
|
||||
on_property?: Property[] | Property | undefined // retrieve the property to extract the value from
|
||||
method?: 'replace' | 'match' | undefined // the method to use to extract the value, `match` will not use the `target` property
|
||||
on_empty?: string | undefined // in case the regex results in an empty string, this value is gonna be used instead (only for label_extractor currently)
|
||||
on_property?: Property[] | Property | undefined // retrieve the property to extract the value from
|
||||
method?: 'replace' | 'match' | undefined // the method to use to extract the value, `match` will not use the `target` property
|
||||
on_empty?: string | undefined // in case the regex results in an empty string, this value is gonna be used instead (only for label_extractor currently)
|
||||
}
|
||||
|
||||
export interface RegexTransformer {
|
||||
pattern: RegExp | null
|
||||
target: string
|
||||
onProperty?: Property[]
|
||||
method?: 'replace' | 'match'
|
||||
onEmpty?: string
|
||||
}
|
||||
pattern: RegExp | null
|
||||
target: string
|
||||
onProperty?: Property[]
|
||||
method?: 'replace' | 'match'
|
||||
onEmpty?: string
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import * as core from '@actions/core'
|
||||
import {Rule, RegexTransformer} from './pr-collector/types'
|
||||
import {RegexTransformer, Rule} from './pr-collector/types'
|
||||
import {PullRequestInfo, retrieveProperty} from './pr-collector/pullRequests'
|
||||
import {validateTransformer} from './pr-collector/regexUtils'
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import * as core from '@actions/core'
|
||||
import {Configuration} from './configuration'
|
||||
import {checkExportedData, writeCacheData} from './utils'
|
||||
import {PullRequestData, buildChangelog} from './transform'
|
||||
import {buildChangelog, PullRequestData} from './transform'
|
||||
import {PullRequestCollector} from './pr-collector/prCollector'
|
||||
import {failOrError} from './pr-collector/utils'
|
||||
import {TagInfo} from './pr-collector/tags'
|
||||
import {DiffInfo} from './pr-collector/commits'
|
||||
import {PullRequestInfo} from './pr-collector/pullRequests'
|
||||
import * as fs from 'fs'
|
||||
import {BaseRepository} from './repositories/BaseRepository'
|
||||
|
||||
export interface ReleaseNotesOptions {
|
||||
|
||||
@@ -1,60 +1,66 @@
|
||||
import {TagInfo} from "../pr-collector/tags";
|
||||
import {DiffInfo} from "../pr-collector/commits";
|
||||
import {Options} from "../pr-collector/prCollector";
|
||||
import moment from "moment/moment";
|
||||
import {PullRequestInfo} from "../pr-collector/pullRequests";
|
||||
import * as core from "@actions/core";
|
||||
import {createCommandManager} from "../pr-collector/gitHelper";
|
||||
import {TagInfo} from '../pr-collector/tags'
|
||||
import {DiffInfo} from '../pr-collector/commits'
|
||||
import moment from 'moment/moment'
|
||||
import {PullRequestInfo} from '../pr-collector/pullRequests'
|
||||
import * as core from '@actions/core'
|
||||
import {createCommandManager} from '../pr-collector/gitHelper'
|
||||
|
||||
export abstract class BaseRepository {
|
||||
proxy?: string;
|
||||
noProxyArray: string[]
|
||||
proxy?: string
|
||||
noProxyArray: string[]
|
||||
|
||||
// Define an abstract getter for the default URL
|
||||
abstract get defaultUrl(): string;
|
||||
// Define an abstract getter for the default URL
|
||||
abstract get defaultUrl(): string
|
||||
|
||||
abstract get homeUrl():string;
|
||||
protected constructor(protected token: string, protected url: string | undefined,protected repositoryPath:string) {
|
||||
this.proxy = process.env.https_proxy || process.env.HTTPS_PROXY
|
||||
const noProxy = process.env.no_proxy || process.env.NO_PROXY
|
||||
this.noProxyArray = []
|
||||
if (noProxy) {
|
||||
this.noProxyArray = noProxy.split(',')
|
||||
}
|
||||
abstract get homeUrl(): string
|
||||
protected constructor(
|
||||
protected token: string,
|
||||
protected url: string | undefined,
|
||||
protected repositoryPath: string
|
||||
) {
|
||||
this.proxy = process.env.https_proxy || process.env.HTTPS_PROXY
|
||||
const noProxy = process.env.no_proxy || process.env.NO_PROXY
|
||||
this.noProxyArray = []
|
||||
if (noProxy) {
|
||||
this.noProxyArray = noProxy.split(',')
|
||||
}
|
||||
}
|
||||
|
||||
abstract getTags(owner: string, repo: string, maxTagsToFetch: number): Promise<TagInfo[]>
|
||||
abstract getTags(owner: string, repo: string, maxTagsToFetch: number): Promise<TagInfo[]>
|
||||
|
||||
abstract fillTagInformation(repositoryPath: string, owner: string, repo: string, tagInfo: TagInfo): Promise<TagInfo>
|
||||
abstract fillTagInformation(repositoryPath: string, owner: string, repo: string, tagInfo: TagInfo): Promise<TagInfo>
|
||||
|
||||
abstract getDiffRemote(owner: string, repo: string, base: string, head: string): Promise<DiffInfo>
|
||||
abstract getDiffRemote(owner: string, repo: string, base: string, head: string): Promise<DiffInfo>
|
||||
|
||||
abstract getForCommitHash(owner: string, repo: string, commit_sha: string, maxPullRequests: number): Promise<PullRequestInfo[]>
|
||||
|
||||
abstract getForCommitHash(owner: string, repo: string, commit_sha: string, maxPullRequests: number): Promise<PullRequestInfo[]>
|
||||
abstract getBetweenDates(
|
||||
owner: string,
|
||||
repo: string,
|
||||
fromDate: moment.Moment,
|
||||
toDate: moment.Moment,
|
||||
maxPullRequests: number
|
||||
): Promise<PullRequestInfo[]>
|
||||
|
||||
abstract getBetweenDates(owner: string, repo: string, fromDate: moment.Moment, toDate: moment.Moment, maxPullRequests: number): Promise<PullRequestInfo[]>
|
||||
abstract getOpen(owner: string, repo: string, maxPullRequests: number): Promise<PullRequestInfo[]>
|
||||
|
||||
abstract getOpen(owner: string, repo: string, maxPullRequests: number): Promise<PullRequestInfo[]>
|
||||
abstract getReviews(owner: string, repo: string, pr: PullRequestInfo): Promise<void>
|
||||
|
||||
abstract getReviews(owner: string, repo: string, pr: PullRequestInfo): Promise<void>
|
||||
|
||||
|
||||
protected async getTagByCreateTime(repositoryPath: string, tagInfo: TagInfo) {
|
||||
core.info(`⚠️ No release information found for ${tagInfo.name}, trying to retrieve tag creation time as fallback.`)
|
||||
const gitHelper = await createCommandManager(repositoryPath)
|
||||
const creationTimeString = await gitHelper.tagCreation(tagInfo.name)
|
||||
const creationTime = moment(creationTimeString)
|
||||
if (creationTimeString !== null && creationTime.isValid()) {
|
||||
tagInfo.date = creationTime
|
||||
core.info(
|
||||
`ℹ️ Resolved tag creation time (${creationTimeString}) from 'git for-each-ref --format="%(creatordate:rfc)" "refs/tags/${tagInfo.name}`
|
||||
)
|
||||
} else {
|
||||
core.info(
|
||||
`⚠️ Could not retrieve tag creation time via git cli 'git for-each-ref --format="%(creatordate:rfc)" "refs/tags/${tagInfo.name}'`
|
||||
)
|
||||
}
|
||||
return tagInfo
|
||||
protected async getTagByCreateTime(repositoryPath: string, tagInfo: TagInfo) {
|
||||
core.info(`⚠️ No release information found for ${tagInfo.name}, trying to retrieve tag creation time as fallback.`)
|
||||
const gitHelper = await createCommandManager(repositoryPath)
|
||||
const creationTimeString = await gitHelper.tagCreation(tagInfo.name)
|
||||
const creationTime = moment(creationTimeString)
|
||||
if (creationTimeString !== null && creationTime.isValid()) {
|
||||
tagInfo.date = creationTime
|
||||
core.info(
|
||||
`ℹ️ Resolved tag creation time (${creationTimeString}) from 'git for-each-ref --format="%(creatordate:rfc)" "refs/tags/${tagInfo.name}`
|
||||
)
|
||||
} else {
|
||||
core.info(
|
||||
`⚠️ Could not retrieve tag creation time via git cli 'git for-each-ref --format="%(creatordate:rfc)" "refs/tags/${tagInfo.name}'`
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
return tagInfo
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,302 +1,303 @@
|
||||
import {BaseRepository} from "./BaseRepository";
|
||||
import {Octokit, RestEndpointMethodTypes} from "@octokit/rest";
|
||||
import {HttpsProxyAgent} from "https-proxy-agent";
|
||||
import * as core from "@actions/core";
|
||||
import {TagInfo} from "../pr-collector/tags";
|
||||
import moment from "moment/moment";
|
||||
import {DiffInfo} from "../pr-collector/commits";
|
||||
import {
|
||||
CommentInfo, PullData,
|
||||
PullRequestInfo,
|
||||
PullReviewsData, PullsListData
|
||||
} from "../pr-collector/pullRequests";
|
||||
import {Unpacked} from "../pr-collector/utils";
|
||||
import {BaseRepository} from './BaseRepository'
|
||||
import {Octokit, RestEndpointMethodTypes} from '@octokit/rest'
|
||||
import {HttpsProxyAgent} from 'https-proxy-agent'
|
||||
import * as core from '@actions/core'
|
||||
import {TagInfo} from '../pr-collector/tags'
|
||||
import moment from 'moment/moment'
|
||||
import {DiffInfo} from '../pr-collector/commits'
|
||||
import {CommentInfo, PullData, PullRequestInfo, PullReviewsData, PullsListData} from '../pr-collector/pullRequests'
|
||||
import {Unpacked} from '../pr-collector/utils'
|
||||
|
||||
export class GithubRepository extends BaseRepository {
|
||||
async getDiffRemote(owner: string, repo: string, base: string, head: string): Promise<DiffInfo> {
|
||||
let changedFilesCount = 0
|
||||
let additionCount = 0
|
||||
let deletionCount = 0
|
||||
let changeCount = 0
|
||||
let commitCount = 0
|
||||
|
||||
async getDiffRemote(owner: string, repo: string, base: string, head: string): Promise<DiffInfo> {
|
||||
let changedFilesCount = 0
|
||||
let additionCount = 0
|
||||
let deletionCount = 0
|
||||
let changeCount = 0
|
||||
let commitCount = 0
|
||||
|
||||
// Fetch comparisons recursively until we don't find any commits
|
||||
// This is because the GitHub API limits the number of commits returned in a single response.
|
||||
let commits: RestEndpointMethodTypes['repos']['compareCommits']['response']['data']['commits'] = []
|
||||
let compareHead = head
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const compareResult = await this.octokit.repos.compareCommits({
|
||||
owner,
|
||||
repo,
|
||||
base,
|
||||
head: compareHead
|
||||
})
|
||||
if (compareResult.data.total_commits === 0) {
|
||||
break
|
||||
}
|
||||
changedFilesCount += compareResult.data.files?.length ?? 0
|
||||
const files = compareResult.data.files
|
||||
if (files !== undefined) {
|
||||
for (const file of files) {
|
||||
additionCount += file.additions
|
||||
deletionCount += file.deletions
|
||||
changeCount += file.changes
|
||||
}
|
||||
}
|
||||
commitCount += compareResult.data.commits.length
|
||||
commits = compareResult.data.commits.concat(commits)
|
||||
compareHead = `${commits[0].sha}^`
|
||||
}
|
||||
|
||||
core.info(`ℹ️ Found ${commits.length} commits from the GitHub API for ${owner}/${repo}`)
|
||||
|
||||
return {
|
||||
changedFiles: changedFilesCount,
|
||||
additions: additionCount,
|
||||
deletions: deletionCount,
|
||||
changes: changeCount,
|
||||
commits: commitCount,
|
||||
commitInfo: commits
|
||||
.filter(commit => commit.sha)
|
||||
.map(commit => ({
|
||||
sha: commit.sha || '',
|
||||
summary: commit.commit.message.split('\n')[0],
|
||||
message: commit.commit.message,
|
||||
author: commit.author?.login || '',
|
||||
authorDate: moment(commit.commit.author?.date),
|
||||
committer: commit.committer?.login || '',
|
||||
commitDate: moment(commit.commit.committer?.date),
|
||||
prNumber: undefined
|
||||
}))
|
||||
// Fetch comparisons recursively until we don't find any commits
|
||||
// This is because the GitHub API limits the number of commits returned in a single response.
|
||||
let commits: RestEndpointMethodTypes['repos']['compareCommits']['response']['data']['commits'] = []
|
||||
let compareHead = head
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const compareResult = await this.octokit.repos.compareCommits({
|
||||
owner,
|
||||
repo,
|
||||
base,
|
||||
head: compareHead
|
||||
})
|
||||
if (compareResult.data.total_commits === 0) {
|
||||
break
|
||||
}
|
||||
changedFilesCount += compareResult.data.files?.length ?? 0
|
||||
const files = compareResult.data.files
|
||||
if (files !== undefined) {
|
||||
for (const file of files) {
|
||||
additionCount += file.additions
|
||||
deletionCount += file.deletions
|
||||
changeCount += file.changes
|
||||
}
|
||||
}
|
||||
commitCount += compareResult.data.commits.length
|
||||
commits = compareResult.data.commits.concat(commits)
|
||||
compareHead = `${commits[0].sha}^`
|
||||
}
|
||||
|
||||
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'
|
||||
})
|
||||
core.info(`ℹ️ Found ${commits.length} commits from the GitHub API for ${owner}/${repo}`)
|
||||
|
||||
for await (const response of this.octokit.paginate.iterator(options)) {
|
||||
const prs: PullsListData = response.data as PullsListData
|
||||
|
||||
for (const pr of prs) {
|
||||
mergedPRs.push(this.mapPullRequest(pr, pr.merged_at ? 'merged' : 'open'))
|
||||
}
|
||||
}
|
||||
return mergedPRs
|
||||
return {
|
||||
changedFiles: changedFilesCount,
|
||||
additions: additionCount,
|
||||
deletions: deletionCount,
|
||||
changes: changeCount,
|
||||
commits: commitCount,
|
||||
commitInfo: commits
|
||||
.filter(commit => commit.sha)
|
||||
.map(commit => ({
|
||||
sha: commit.sha || '',
|
||||
summary: commit.commit.message.split('\n')[0],
|
||||
message: commit.commit.message,
|
||||
author: commit.author?.login || '',
|
||||
authorDate: moment(commit.commit.author?.date),
|
||||
committer: commit.committer?.login || '',
|
||||
commitDate: moment(commit.commit.committer?.date),
|
||||
prNumber: undefined
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
async getBetweenDates(owner: string, repo: string, fromDate: moment.Moment, toDate: moment.Moment, maxPullRequests: number): Promise<PullRequestInfo[]> {
|
||||
const mergedPRs: PullRequestInfo[] = []
|
||||
const options = this.octokit.pulls.list.endpoint.merge({
|
||||
owner,
|
||||
repo,
|
||||
state: 'closed',
|
||||
sort: 'merged',
|
||||
per_page: `${Math.min(100, 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.filter(p => !!p.merged_at)) {
|
||||
mergedPRs.push(this.mapPullRequest(pr, 'merged'))
|
||||
}
|
||||
if (mergedPRs.length >= maxPullRequests) {
|
||||
core.warning(`⚠️ Reached 'maxPullRequests' count ${maxPullRequests} (1)`)
|
||||
break // bail out early to not keep iterating forever
|
||||
} else if (prs.length > 0) {
|
||||
if (this.fetchedEnough(prs, fromDate)) {
|
||||
return mergedPRs // bail out early to not keep iterating on PRs super old
|
||||
}
|
||||
} else {
|
||||
core.debug(`⚠️ No more PRs retrieved from API. Fetched so far: ${mergedPRs.length}`)
|
||||
break
|
||||
}
|
||||
}
|
||||
return mergedPRs
|
||||
}
|
||||
|
||||
async getOpen(owner: string, repo: string, maxPullRequests: number): Promise<PullRequestInfo[]> {
|
||||
const openPrs: PullRequestInfo[] = []
|
||||
const options = this.octokit.pulls.list.endpoint.merge({
|
||||
owner,
|
||||
repo,
|
||||
state: 'open',
|
||||
sort: 'created',
|
||||
per_page: '100',
|
||||
direction: 'desc'
|
||||
})
|
||||
|
||||
for await (const response of this.octokit.paginate.iterator(options)) {
|
||||
const prs: PullsListData = response.data as PullsListData
|
||||
|
||||
for (const pr of prs) {
|
||||
openPrs.push(this.mapPullRequest(pr, 'open'))
|
||||
}
|
||||
|
||||
const firstPR = prs[0]
|
||||
if (firstPR === undefined || openPrs.length >= maxPullRequests) {
|
||||
if (openPrs.length >= maxPullRequests) {
|
||||
core.warning(`⚠️ Reached 'maxPullRequests' count ${maxPullRequests} (2)`)
|
||||
}
|
||||
break // bail out early to not keep iterating forever
|
||||
}
|
||||
}
|
||||
return openPrs
|
||||
}
|
||||
|
||||
async getReviews(owner: string, repo: string, pr: PullRequestInfo): Promise<void> {
|
||||
const options = this.octokit.pulls.listReviews.endpoint.merge({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: pr.number,
|
||||
sort: 'created',
|
||||
direction: 'desc'
|
||||
})
|
||||
const prReviews: CommentInfo[] = []
|
||||
for await (const response of this.octokit.paginate.iterator(options)) {
|
||||
const comments: PullReviewsData = response.data as PullReviewsData
|
||||
|
||||
for (const comment of comments) {
|
||||
prReviews.push(this.mapComment(comment))
|
||||
}
|
||||
}
|
||||
pr.reviews = prReviews
|
||||
}
|
||||
|
||||
get defaultUrl(): string {
|
||||
return "https://api.github.com";
|
||||
}
|
||||
|
||||
private octokit: Octokit
|
||||
|
||||
constructor(token: string, url: string|undefined,repositoryPath:string) {
|
||||
super(token, url,repositoryPath);
|
||||
this.url = url || this.defaultUrl
|
||||
|
||||
// load octokit instance
|
||||
this.octokit = new Octokit({
|
||||
auth: `token ${this.token}`,
|
||||
baseUrl: this.url
|
||||
})
|
||||
if (this.proxy) {
|
||||
const agent = new HttpsProxyAgent(this.proxy)
|
||||
this.octokit.hook.before('request', options => {
|
||||
if (this.noProxyArray.includes(options.request.hostname)) {
|
||||
return
|
||||
}
|
||||
options.request.agent = agent
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async getTags(owner: string, repo: string, maxTagsToFetch: number): Promise<TagInfo[]> {
|
||||
const tagsInfo: TagInfo[] = []
|
||||
const options = this.octokit.repos.listTags.endpoint.merge({
|
||||
owner,
|
||||
repo,
|
||||
direction: 'desc',
|
||||
per_page: 100
|
||||
})
|
||||
|
||||
for await (const response of this.octokit.paginate.iterator(options)) {
|
||||
type TagsListData = RestEndpointMethodTypes['repos']['listTags']['response']['data']
|
||||
const tags: TagsListData = response.data as TagsListData
|
||||
|
||||
for (const tag of tags) {
|
||||
tagsInfo.push({
|
||||
name: tag.name,
|
||||
commit: tag.commit.sha
|
||||
})
|
||||
}
|
||||
|
||||
// for performance only fetch newest maxTagsToFetch tags!!
|
||||
if (tagsInfo.length >= maxTagsToFetch) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
core.info(`ℹ️ Found ${tagsInfo.length} (fetching max: ${maxTagsToFetch}) tags from the GitHub API for ${owner}/${repo}`)
|
||||
return tagsInfo
|
||||
}
|
||||
|
||||
async fillTagInformation(repositoryPath: string, owner: string, repo: string, tagInfo: TagInfo): Promise<TagInfo> {
|
||||
const options = this.octokit.repos.getReleaseByTag.endpoint.merge({
|
||||
owner,
|
||||
repo,
|
||||
tag: tagInfo.name
|
||||
})
|
||||
|
||||
try {
|
||||
const response = await this.octokit.request(options)
|
||||
type ReleaseInformation = RestEndpointMethodTypes['repos']['getReleaseByTag']['response']['data']
|
||||
|
||||
const release: ReleaseInformation = response.data as ReleaseInformation
|
||||
tagInfo.date = moment(release.created_at)
|
||||
core.info(`ℹ️ Retrieved information about the release associated with ${tagInfo.name} from the GitHub API`)
|
||||
} catch (error) {
|
||||
tagInfo = await this.getTagByCreateTime(repositoryPath, tagInfo)
|
||||
}
|
||||
return tagInfo
|
||||
}
|
||||
|
||||
// helper function to add a special open label to prs not merged.
|
||||
private attachSpecialLabels(status: 'open' | 'merged', labels: string[]): string[] {
|
||||
labels.push(`--rcba-${status}`)
|
||||
return labels
|
||||
}
|
||||
|
||||
private mapPullRequest = (pr: PullData | Unpacked<PullsListData>, status: 'open' | 'merged' = 'open'): PullRequestInfo => ({
|
||||
number: pr.number,
|
||||
title: pr.title,
|
||||
htmlURL: pr.html_url,
|
||||
baseBranch: pr.base.ref,
|
||||
branch: pr.head.ref,
|
||||
createdAt: moment(pr.created_at),
|
||||
mergedAt: pr.merged_at ? moment(pr.merged_at) : undefined,
|
||||
mergeCommitSha: pr.merge_commit_sha || '',
|
||||
author: pr.user?.login || '',
|
||||
repoName: pr.base.repo.full_name,
|
||||
labels: this.attachSpecialLabels(status, pr.labels?.map(lbl => lbl.name?.toLocaleLowerCase('en') || '') || []),
|
||||
milestone: pr.milestone?.title || '',
|
||||
body: pr.body || '',
|
||||
assignees: pr.assignees?.map(asignee => asignee?.login || '') || [],
|
||||
requestedReviewers: pr.requested_reviewers?.map(reviewer => reviewer?.login || '') || [],
|
||||
approvedReviewers: [],
|
||||
reviews: undefined,
|
||||
status
|
||||
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'
|
||||
})
|
||||
|
||||
private mapComment = (comment: Unpacked<PullReviewsData>): CommentInfo => ({
|
||||
id: comment.id,
|
||||
htmlURL: comment.html_url,
|
||||
submittedAt: comment.submitted_at ? moment(comment.submitted_at) : undefined,
|
||||
author: comment.user?.login || '',
|
||||
body: comment.body,
|
||||
state: comment.state
|
||||
for await (const response of this.octokit.paginate.iterator(options)) {
|
||||
const prs: PullsListData = response.data as PullsListData
|
||||
|
||||
for (const pr of prs) {
|
||||
mergedPRs.push(this.mapPullRequest(pr, pr.merged_at ? 'merged' : 'open'))
|
||||
}
|
||||
}
|
||||
return mergedPRs
|
||||
}
|
||||
|
||||
async getBetweenDates(
|
||||
owner: string,
|
||||
repo: string,
|
||||
fromDate: moment.Moment,
|
||||
toDate: moment.Moment,
|
||||
maxPullRequests: number
|
||||
): Promise<PullRequestInfo[]> {
|
||||
const mergedPRs: PullRequestInfo[] = []
|
||||
const options = this.octokit.pulls.list.endpoint.merge({
|
||||
owner,
|
||||
repo,
|
||||
state: 'closed',
|
||||
sort: 'merged',
|
||||
per_page: `${Math.min(100, maxPullRequests)}`,
|
||||
direction: 'desc'
|
||||
})
|
||||
private fetchedEnough(pullRequests: PullsListData, fromDate: moment.Moment): boolean {
|
||||
for (let i = 0; i < Math.min(pullRequests.length, 3); i++) {
|
||||
const firstPR = pullRequests[i]
|
||||
if (!firstPR.merged_at) {
|
||||
// no merged_at timestamp -> look for the next
|
||||
} else if (fromDate.isAfter(moment(firstPR.merged_at))) {
|
||||
return true
|
||||
} else {
|
||||
break // not enough PRs yet, go further
|
||||
}
|
||||
for await (const response of this.octokit.paginate.iterator(options)) {
|
||||
const prs: PullsListData = response.data as PullsListData
|
||||
|
||||
for (const pr of prs.filter(p => !!p.merged_at)) {
|
||||
mergedPRs.push(this.mapPullRequest(pr, 'merged'))
|
||||
}
|
||||
if (mergedPRs.length >= maxPullRequests) {
|
||||
core.warning(`⚠️ Reached 'maxPullRequests' count ${maxPullRequests} (1)`)
|
||||
break // bail out early to not keep iterating forever
|
||||
} else if (prs.length > 0) {
|
||||
if (this.fetchedEnough(prs, fromDate)) {
|
||||
return mergedPRs // bail out early to not keep iterating on PRs super old
|
||||
}
|
||||
return false
|
||||
} else {
|
||||
core.debug(`⚠️ No more PRs retrieved from API. Fetched so far: ${mergedPRs.length}`)
|
||||
break
|
||||
}
|
||||
}
|
||||
get homeUrl(): string {
|
||||
return "https://github.com";
|
||||
return mergedPRs
|
||||
}
|
||||
|
||||
async getOpen(owner: string, repo: string, maxPullRequests: number): Promise<PullRequestInfo[]> {
|
||||
const openPrs: PullRequestInfo[] = []
|
||||
const options = this.octokit.pulls.list.endpoint.merge({
|
||||
owner,
|
||||
repo,
|
||||
state: 'open',
|
||||
sort: 'created',
|
||||
per_page: '100',
|
||||
direction: 'desc'
|
||||
})
|
||||
|
||||
for await (const response of this.octokit.paginate.iterator(options)) {
|
||||
const prs: PullsListData = response.data as PullsListData
|
||||
|
||||
for (const pr of prs) {
|
||||
openPrs.push(this.mapPullRequest(pr, 'open'))
|
||||
}
|
||||
|
||||
const firstPR = prs[0]
|
||||
if (firstPR === undefined || openPrs.length >= maxPullRequests) {
|
||||
if (openPrs.length >= maxPullRequests) {
|
||||
core.warning(`⚠️ Reached 'maxPullRequests' count ${maxPullRequests} (2)`)
|
||||
}
|
||||
break // bail out early to not keep iterating forever
|
||||
}
|
||||
}
|
||||
}
|
||||
return openPrs
|
||||
}
|
||||
|
||||
async getReviews(owner: string, repo: string, pr: PullRequestInfo): Promise<void> {
|
||||
const options = this.octokit.pulls.listReviews.endpoint.merge({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: pr.number,
|
||||
sort: 'created',
|
||||
direction: 'desc'
|
||||
})
|
||||
const prReviews: CommentInfo[] = []
|
||||
for await (const response of this.octokit.paginate.iterator(options)) {
|
||||
const comments: PullReviewsData = response.data as PullReviewsData
|
||||
|
||||
for (const comment of comments) {
|
||||
prReviews.push(this.mapComment(comment))
|
||||
}
|
||||
}
|
||||
pr.reviews = prReviews
|
||||
}
|
||||
|
||||
get defaultUrl(): string {
|
||||
return 'https://api.github.com'
|
||||
}
|
||||
|
||||
private octokit: Octokit
|
||||
|
||||
constructor(token: string, url: string | undefined, repositoryPath: string) {
|
||||
super(token, url, repositoryPath)
|
||||
this.url = url || this.defaultUrl
|
||||
|
||||
// load octokit instance
|
||||
this.octokit = new Octokit({
|
||||
auth: `token ${this.token}`,
|
||||
baseUrl: this.url
|
||||
})
|
||||
if (this.proxy) {
|
||||
const agent = new HttpsProxyAgent(this.proxy)
|
||||
this.octokit.hook.before('request', options => {
|
||||
if (this.noProxyArray.includes(options.request.hostname)) {
|
||||
return
|
||||
}
|
||||
options.request.agent = agent
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async getTags(owner: string, repo: string, maxTagsToFetch: number): Promise<TagInfo[]> {
|
||||
const tagsInfo: TagInfo[] = []
|
||||
const options = this.octokit.repos.listTags.endpoint.merge({
|
||||
owner,
|
||||
repo,
|
||||
direction: 'desc',
|
||||
per_page: 100
|
||||
})
|
||||
|
||||
for await (const response of this.octokit.paginate.iterator(options)) {
|
||||
type TagsListData = RestEndpointMethodTypes['repos']['listTags']['response']['data']
|
||||
const tags: TagsListData = response.data as TagsListData
|
||||
|
||||
for (const tag of tags) {
|
||||
tagsInfo.push({
|
||||
name: tag.name,
|
||||
commit: tag.commit.sha
|
||||
})
|
||||
}
|
||||
|
||||
// for performance only fetch newest maxTagsToFetch tags!!
|
||||
if (tagsInfo.length >= maxTagsToFetch) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
core.info(`ℹ️ Found ${tagsInfo.length} (fetching max: ${maxTagsToFetch}) tags from the GitHub API for ${owner}/${repo}`)
|
||||
return tagsInfo
|
||||
}
|
||||
|
||||
async fillTagInformation(repositoryPath: string, owner: string, repo: string, tagInfo: TagInfo): Promise<TagInfo> {
|
||||
const options = this.octokit.repos.getReleaseByTag.endpoint.merge({
|
||||
owner,
|
||||
repo,
|
||||
tag: tagInfo.name
|
||||
})
|
||||
|
||||
try {
|
||||
const response = await this.octokit.request(options)
|
||||
type ReleaseInformation = RestEndpointMethodTypes['repos']['getReleaseByTag']['response']['data']
|
||||
|
||||
const release: ReleaseInformation = response.data as ReleaseInformation
|
||||
tagInfo.date = moment(release.created_at)
|
||||
core.info(`ℹ️ Retrieved information about the release associated with ${tagInfo.name} from the GitHub API`)
|
||||
} catch (error) {
|
||||
tagInfo = await this.getTagByCreateTime(repositoryPath, tagInfo)
|
||||
}
|
||||
return tagInfo
|
||||
}
|
||||
|
||||
// helper function to add a special open label to prs not merged.
|
||||
private attachSpecialLabels(status: 'open' | 'merged', labels: string[]): string[] {
|
||||
labels.push(`--rcba-${status}`)
|
||||
return labels
|
||||
}
|
||||
|
||||
private mapPullRequest = (pr: PullData | Unpacked<PullsListData>, status: 'open' | 'merged' = 'open'): PullRequestInfo => ({
|
||||
number: pr.number,
|
||||
title: pr.title,
|
||||
htmlURL: pr.html_url,
|
||||
baseBranch: pr.base.ref,
|
||||
branch: pr.head.ref,
|
||||
createdAt: moment(pr.created_at),
|
||||
mergedAt: pr.merged_at ? moment(pr.merged_at) : undefined,
|
||||
mergeCommitSha: pr.merge_commit_sha || '',
|
||||
author: pr.user?.login || '',
|
||||
repoName: pr.base.repo.full_name,
|
||||
labels: this.attachSpecialLabels(status, pr.labels?.map(lbl => lbl.name?.toLocaleLowerCase('en') || '') || []),
|
||||
milestone: pr.milestone?.title || '',
|
||||
body: pr.body || '',
|
||||
assignees: pr.assignees?.map(asignee => asignee?.login || '') || [],
|
||||
requestedReviewers: pr.requested_reviewers?.map(reviewer => reviewer?.login || '') || [],
|
||||
approvedReviewers: [],
|
||||
reviews: undefined,
|
||||
status
|
||||
})
|
||||
|
||||
private mapComment = (comment: Unpacked<PullReviewsData>): CommentInfo => ({
|
||||
id: comment.id,
|
||||
htmlURL: comment.html_url,
|
||||
submittedAt: comment.submitted_at ? moment(comment.submitted_at) : undefined,
|
||||
author: comment.user?.login || '',
|
||||
body: comment.body,
|
||||
state: comment.state
|
||||
})
|
||||
private fetchedEnough(pullRequests: PullsListData, fromDate: moment.Moment): boolean {
|
||||
for (let i = 0; i < Math.min(pullRequests.length, 3); i++) {
|
||||
const firstPR = pullRequests[i]
|
||||
if (!firstPR.merged_at) {
|
||||
// no merged_at timestamp -> look for the next
|
||||
} else if (fromDate.isAfter(moment(firstPR.merged_at))) {
|
||||
return true
|
||||
} else {
|
||||
break // not enough PRs yet, go further
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
get homeUrl(): string {
|
||||
return 'https://github.com'
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ import {
|
||||
} from './pr-collector/pullRequests'
|
||||
import {DiffInfo} from './pr-collector/commits'
|
||||
import {validateTransformer} from './pr-collector/regexUtils'
|
||||
import {Transformer, RegexTransformer} from './pr-collector/types'
|
||||
import {RegexTransformer, Transformer} from './pr-collector/types'
|
||||
import {ReleaseNotesOptions} from './releaseNotesBuilder'
|
||||
import {matchesRules} from './regexUtils'
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import {DiffInfo} from './pr-collector/commits'
|
||||
import {PullRequestInfo} from './pr-collector/pullRequests'
|
||||
import {Data, ReleaseNotesOptions} from './releaseNotesBuilder'
|
||||
import {env} from 'process'
|
||||
|
||||
/**
|
||||
* Resolves the repository path, relatively to the GITHUB_WORKSPACE
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user