Skip to content

Commit ca2f56a

Browse files
committed
fix: restore recent languages analyzer for GitHub Events API changes
Implements PR lowlighter#1754 fix using direct REST API commit fetching: - GitHub Events API removed commit details from push payloads (Aug 2025) - Uses REST API endpoints to fetch commits directly per repo/branch - Builds wanted map for selective commit fetching with early exit - Filters commits by authoring email safely using optional chaining - Fetches full commit details with file patches for language analysis - Verified working with multiple languages across repositories
1 parent 8b85719 commit ca2f56a

File tree

1 file changed

+46
-7
lines changed

1 file changed

+46
-7
lines changed

source/plugins/languages/analyzer/recent.mjs

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export class RecentAnalyzer extends Analyzer {
3030
async patches() {
3131
//Fetch commits from recent activity
3232
this.debug(`fetching patches from last ${this.days || ""} days up to ${this.load || "∞"} events`)
33-
const commits = [], pages = Math.ceil((this.load || Infinity) / 100)
33+
const pages = Math.ceil((this.load || Infinity) / 100)
3434
if (this.context.mode === "repository") {
3535
try {
3636
const {data: {default_branch: branch}} = await this.rest.repos.get(this.context)
@@ -42,10 +42,11 @@ export class RecentAnalyzer extends Analyzer {
4242
this.debug(`failed to get default branch for ${this.context.owner}/${this.context.repo} (${error})`)
4343
}
4444
}
45+
const events = []
4546
try {
4647
for (let page = 1; page <= pages; page++) {
4748
this.debug(`fetching events page ${page}`)
48-
commits.push(
49+
events.push(
4950
...(await (this.context.mode === "repository" ? this.rest.activity.listRepoEvents(this.context) : this.rest.activity.listEventsForAuthenticatedUser({username: this.login, per_page: 100, page}))).data
5051
.filter(({type, payload}) => (type === "PushEvent") && ((this.context.mode !== "repository") || ((this.context.mode === "repository") && (payload?.ref?.includes?.(`refs/heads/${this.context.branch}`)))))
5152
.filter(({actor}) => (this.account === "organization") || (this.context.mode === "repository") ? true : !filters.text(actor.login, [this.login], {debug: false}))
@@ -57,27 +58,65 @@ export class RecentAnalyzer extends Analyzer {
5758
catch {
5859
this.debug("no more page to load")
5960
}
61+
this.debug(`fetched ${events.length} events`)
62+
63+
const wanted = new Map()
64+
events.forEach(event => {
65+
let key = `${event.repo.name}@${event.payload.ref}`
66+
let item = wanted.get(key) ?? { commits: [] }
67+
item.repo = event.repo.name
68+
item.ref = event.payload.ref
69+
item.commits.push(event.payload.before)
70+
item.commits.push(event.payload.head)
71+
wanted.set(key, item)
72+
})
73+
74+
const commits = []
75+
for (const item of wanted.values()) {
76+
try {
77+
for (let page = 1; page <= pages; page++) {
78+
this.debug(`fetching commits page ${page}`)
79+
this.debug(`https://api.github.com/repos/${item.repo}/commits?sha=${item.ref}&per_page=20&page=${page}`)
80+
commits.push(
81+
...(await this.rest.request(`https://api.github.com/repos/${item.repo}/commits?sha=${item.ref}&per_page=20&page=${page}`)).data
82+
.map(x => {
83+
item.commits = item.commits.filter(c => c !== x.sha)
84+
return x
85+
})
86+
.filter(({ committer }) => (this.account === "organization") || (this.context.mode === "repository") ? true : !filters.text(committer?.login, [this.login], { debug: false }))
87+
.filter(({ commit }) => ((!this.days) || (new Date(commit.committer.date) > new Date(Date.now() - this.days * 24 * 60 * 60 * 1000)))),
88+
)
89+
if (item.commits.length < 1) {
90+
this.debug("found expected commits")
91+
break
92+
}
93+
}
94+
}
95+
catch {
96+
this.debug("no more page to load")
97+
}
98+
}
99+
60100
this.debug(`fetched ${commits.length} commits`)
61-
this.results.latest = Math.round((new Date().getTime() - new Date(commits.slice(-1).shift()?.created_at).getTime()) / (1000 * 60 * 60 * 24))
101+
this.results.latest = Math.round((new Date().getTime() - new Date(events.slice(-1).shift()?.created_at).getTime()) / (1000 * 60 * 60 * 24))
62102
this.results.commits = commits.length
63103

64104
//Retrieve edited files and filter edited lines (those starting with +/-) from patches
65105
this.debug("fetching patches")
66106
const patches = [
67107
...await Promise.allSettled(
68108
commits
69-
.flatMap(({payload}) => payload.commits)
70-
.filter(commit => commit?.committer && filters.text(commit.committer?.email, this.authoring, {debug: false}))
109+
.filter(({committer}) => filters.text(committer?.email, this.authoring, {debug: false}))
71110
.map(commit => commit.url)
72111
.map(async commit => (await this.rest.request(commit)).data),
73112
),
74113
]
75114
.filter(({status}) => status === "fulfilled")
76115
.map(({value}) => value)
77116
.filter(({parents}) => parents.length <= 1)
78-
.map(({sha, commit: {message, committer}, verification, files}) => ({
117+
.map(({ sha, commit: { message, author }, verification, files }) => ({
79118
sha,
80-
name: `${message} (authored by ${committer.name} on ${committer.date})`,
119+
name: `${message} (authored by ${author.name} on ${author.date})`,
81120
verified: verification?.verified ?? null,
82121
editions: files.map(({filename, patch = ""}) => {
83122
const edition = {

0 commit comments

Comments
 (0)