style: code formatting

This commit is contained in:
Ankio
2023-11-01 11:05:11 +08:00
parent 08cf89bc11
commit e08045ee77
13 changed files with 520 additions and 516 deletions
+288 -287
View File
@@ -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'
}
}